请写一篇关于【Spring Security基础:快速搭建安全认证系统】的技术文章

杨智★
2025-12-17 23:38
阅读 1656

开篇:去年十月,我在广州老西关的出租屋里差点哭出来

时间是去年十月的一个周四晚上,10点23分。窗外是恩宁路熟悉的骑楼和零星闪烁的霓虹灯,屋里是我那台服役五年的联想ThinkPad,风扇声比珠江夜游船的马达还响。我刚被项目经理甩来一个新需求:“下周上线前,必须加上登录认证,不能让游客随便看后台数据。”
我盯着屏幕,手心冒汗。入职三个月,这是我第一次独立负责一个模块。而我对“安全”俩字的理解,还停留在“密码别设成123456”这种层面。

老婆在隔壁房间刷《甄嬛传》重播——这是她怀孕后唯一的娱乐。房租3500,房贷6800,孩子预产期还有四个月。我月薪从转行前做建材销售的15k涨到了现在的22k,但离“安稳”还差十万八千里。
我当时真的慌了。

转行程序员?我是被生活逼上梁山的

先自我介绍一下:我,阿强,30岁,土生土长广州人,荔湾老西关仔。以前在十三行附近卖瓷砖胶,嘴皮子利索,酒量能打,但KPI压得喘不过气。疫情三年,行业崩盘,我看着同行一个个转行送外卖、开滴滴,心里发毛。

2022年春节,老婆一句“要不你学编程吧,听说工资高”彻底改变了我的人生轨迹。我咬牙报了个线上培训班,白天跑客户,晚上啃Java,凌晨三点还在背HashMap原理。半年后,靠着死记硬背+项目包装,终于拿下一家本地SaaS公司的offer,成了初级Java开发。

但现实很快给我上了一课:培训班教的是Hello World,公司要的是Security + JWT + RBAC。

那个“安全认证”的需求,差点让我原地退役

回到那个周四晚上。我打开公司GitLab,翻遍了旧代码,发现之前的登录模块用的是最原始的Session + Filter,连CSRF防护都没有。更离谱的是,测试环境居然开着“admin/admin”这种账号。

我心想:这不行啊,万一被黑了,我第一个背锅。

于是开始疯狂搜索:“Spring Security 入门”、“如何加登录认证”、“Spring Boot 安全教程”。结果呢?一堆官方文档像天书,B站视频要么太浅(只讲@EnableWebSecurity),要么直接上OAuth2 + Redis + 微服务,看得我头大。

最讽刺的是,我还看到一篇标题叫《十分钟搞定Spring Security》的文章——我花了整整三天,连登录页都跳不进去。

期间,我和组里唯一比我早半年入职的小林请教。他瞥了一眼我的配置类,冷笑一声:“你这都没配PasswordEncoder,密码存明文?等着被审计骂死吧。”
我脸一红,赶紧改。

为什么我要写这篇“非典型”教程?

说实话,市面上关于Spring Security的教程多如牛毛,但90%都是站在“专家视角”写的——假设你已经懂FilterChain、AuthenticationProvider、UserDetailsService这些概念。可对于我这种半路出家的“老广码农”,光是搞懂“为什么需要重写loadUserByUsername”就花了两天。

所以今天,我不讲理论,不堆术语,就拿一个真实的小项目——我们公司内部用的“客户反馈管理系统”(其实就是个带CRUD的表格)——来手把手演示:如何用最少的代码,快速搭起一个能用的安全认证系统。

关键词来了:这不是“最佳实践”,而是“最快能跑起来的实践”。

项目背景 & 技术栈

  • 公司:某广州本地SaaS创业公司(15人团队)
  • 项目:客户反馈管理后台(内部使用)
  • 技术栈:Spring Boot 2.7 + MyBatis + MySQL + Thymeleaf(对,我们还没前后端分离)
  • 需求:只有登录用户才能访问 /dashboard 及其子页面;管理员能删数据,普通员工只能看

注意:我们不用JWT!不用OAuth2!就用最朴素的Form Login + Session。
为什么?因为项目小、用户少、迭代快。别一上来就搞复杂架构,那是给自己挖坑。

实操:三步搞定基础认证(附真实代码)

第一步:加依赖,别漏了Thymeleaf的Spring Security标签

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</td>
</tr>
<tr>
<td><dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</td>
</tr>

血泪教训:第一次我忘了加thymeleaf-extras,结果在页面上用sec:authorize="hasRole('ADMIN')"死活不生效,还以为是权限配置错了,debug到凌晨两点。

第二步:写个UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库查用户
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 注意:角色要加前缀 "ROLE_"
        List<GrantedAuthority> authorities = 
            AuthorityUtils.createAuthorityList("ROLE_" + user.getRole());
        
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(), // 这里存的是BCrypt加密后的密文!
            authorities
        );
    }
}

关键点

  • 数据库里的password字段必须是BCrypt加密后的字符串(后面会说怎么生成)
  • 角色名必须加 ROLE_ 前缀,否则 hasRole('ADMIN') 会失效(这是Spring Security的坑)

第三步:配置SecurityConfig(核心!)

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/login", "/css/**", "/js/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login") // 自定义登录页
                .defaultSuccessUrl("/dashboard", true)
                .failureUrl("/login?error=true")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/login?logout=true")
                .permitAll()
            )
            .csrf().disable(); // 注意:生产环境别这么干!

        return http.build();
    }
}

逐行解释(这才是新人需要的):

  • .requestMatchers("/login", ...).permitAll():这些路径不用登录
  • .requestMatchers("/admin/**").hasRole("ADMIN"):/admin开头的路径,必须是ADMIN角色
  • .formLogin():启用表单登录,指定登录页、成功跳转页等
  • .csrf().disable()仅用于开发环境! 正式上线必须开启CSRF防护(但Thymeleaf表单要加<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

附:怎么生成BCrypt密码?

写个临时Controller,跑一次就删:

@RestController
public class TempController {
    @Autowired
    private PasswordEncoder encoder;

    @GetMapping("/encode/{pwd}")
    public String encode(@PathVariable String pwd) {
        return encoder.encode(pwd); // 比如输入 /encode/123456,返回 $2a$10$xxxx
    }
}

然后把返回的字符串手动塞进数据库。千万别在生产代码里留这种接口!

页面怎么控制显示?Thymeleaf + Security标签

登录页(login.html)很简单,就是个form:

<form th:action="@{/login}" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit">登录</button>
</form>

在dashboard页面,根据角色显示不同内容:

<div sec:authorize="hasRole('ADMIN')">
    <button onclick="deleteFeedback()">删除</button>
</div>

<span sec:authentication="name">,欢迎回来!</span>

那一刻,当我用自己的账号(ROLE_EMPLOYEE)登录,看不到删除按钮;切换admin账号就能看到——我差点跳起来! 这就是权限控制的魅力啊。

吐槽时间:Spring Security到底难在哪?

  1. 抽象层级太高:AuthenticationManager、ProviderManager、DaoAuthenticationProvider……名字一个比一个长,新人根本分不清谁调谁。
  2. 默认行为反直觉:比如登录失败默认跳到/login?error,但如果你没配failureUrl,它会返回401,前端一脸懵。
  3. 文档太“官方”:Spring官方文档假设你已经读过《Spring实战》第5版,但现实是很多人连Filter都没写过。

我的建议:别死磕源码!先跑通,再优化。 就像我学骑自行车,先能不摔,再学漂移。

关于“Go”的插曲:为什么我没转Go?

有朋友问我:“既然Java生态这么复杂,不如转Go?Gin + JWT几行代码搞定认证。”

我试过。去年底用Go写了个小工具,确实简洁。但现实是:我们公司技术栈是Java,团队没人会Go,HR也不会为Go岗位多开5k工资。

转行已经让我损失了两年职业黄金期,现在不是追求“技术理想”的时候,而是“稳定交付 + 涨薪”。等房贷还清、孩子上幼儿园,我再考虑折腾Rust或Go也不迟。

生存面前,优雅都是奢侈品。

总结:给和我一样的“半路码农”几点真心话

  1. 别怕用“简单方案”:Spring Security的Form Login虽然“老土”,但对内部系统完全够用。别为了炫技上OAuth2,最后自己维护不来。
  2. 安全无小事,但可以分阶段做:第一阶段先保证“没登录进不去”,第二阶段再加CSRF、密码策略、日志审计。
  3. 善用社区,但别全信:Stack Overflow的答案可能过时,GitHub demo可能跑不起来。自己动手,丰衣足食。
  4. 你的价值不在“会多少框架”,而在“能解决业务问题”:老板不在乎你用Spring Security还是Shiro,他只在乎明天上线不崩。

最后:我在珠江边想明白的事

上周五晚上,我又加班到十点。走出公司大楼,沿着珠江散步,看着对面广州塔的灯光。突然想起三个月前那个在出租屋崩溃的自己。

技术这条路,没有捷径。但每解决一个问题,你就离“不被焦虑支配”近了一步。

Spring Security只是我转行路上的一座小山。后面还有分布式、高并发、架构设计……但我不怕了。因为我知道,只要能跑通第一个认证页面,就能跑通第二个、第三个。

共勉吧,各位和我一样“半路出家”的战友。
我们在代码的世界里,慢慢扎根,静静生长。

P.S. 如果你也在广州,想找人一起喝早茶聊技术(或者吐槽产品经理),欢迎私信。一盅两件,胜过千言万语。

评论 0

最热最新
暂无评论
杨智★Lv.1
0
影响力
0
文章
0
粉丝