请写一篇关于【Spring Security基础:快速搭建安全认证系统】的技术文章
开篇:去年十月,我在广州老西关的出租屋里差点哭出来
时间是去年十月的一个周四晚上,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到底难在哪?
- 抽象层级太高:AuthenticationManager、ProviderManager、DaoAuthenticationProvider……名字一个比一个长,新人根本分不清谁调谁。
- 默认行为反直觉:比如登录失败默认跳到/login?error,但如果你没配failureUrl,它会返回401,前端一脸懵。
- 文档太“官方”:Spring官方文档假设你已经读过《Spring实战》第5版,但现实是很多人连Filter都没写过。
我的建议:别死磕源码!先跑通,再优化。 就像我学骑自行车,先能不摔,再学漂移。
关于“Go”的插曲:为什么我没转Go?
有朋友问我:“既然Java生态这么复杂,不如转Go?Gin + JWT几行代码搞定认证。”
我试过。去年底用Go写了个小工具,确实简洁。但现实是:我们公司技术栈是Java,团队没人会Go,HR也不会为Go岗位多开5k工资。
转行已经让我损失了两年职业黄金期,现在不是追求“技术理想”的时候,而是“稳定交付 + 涨薪”。等房贷还清、孩子上幼儿园,我再考虑折腾Rust或Go也不迟。
生存面前,优雅都是奢侈品。
总结:给和我一样的“半路码农”几点真心话
- 别怕用“简单方案”:Spring Security的Form Login虽然“老土”,但对内部系统完全够用。别为了炫技上OAuth2,最后自己维护不来。
- 安全无小事,但可以分阶段做:第一阶段先保证“没登录进不去”,第二阶段再加CSRF、密码策略、日志审计。
- 善用社区,但别全信:Stack Overflow的答案可能过时,GitHub demo可能跑不起来。自己动手,丰衣足食。
- 你的价值不在“会多少框架”,而在“能解决业务问题”:老板不在乎你用Spring Security还是Shiro,他只在乎明天上线不崩。
最后:我在珠江边想明白的事
上周五晚上,我又加班到十点。走出公司大楼,沿着珠江散步,看着对面广州塔的灯光。突然想起三个月前那个在出租屋崩溃的自己。
技术这条路,没有捷径。但每解决一个问题,你就离“不被焦虑支配”近了一步。
Spring Security只是我转行路上的一座小山。后面还有分布式、高并发、架构设计……但我不怕了。因为我知道,只要能跑通第一个认证页面,就能跑通第二个、第三个。
共勉吧,各位和我一样“半路出家”的战友。
我们在代码的世界里,慢慢扎根,静静生长。
P.S. 如果你也在广州,想找人一起喝早茶聊技术(或者吐槽产品经理),欢迎私信。一盅两件,胜过千言万语。

评论 0