Spring Security基础:快速搭建安全认证系统
大家好,我是老张,一个35岁还在一线敲代码的“高龄”程序员。去年十月,我从杭州搬回了山东老家——一个小县城,靠着远程办公,每月省下3500块房租不说,还能每天陪老婆孩子吃饭。说实话,以前在大厂卷到凌晨是常态,现在下午五点半准时关电脑去接娃放学,感觉人生终于有了点“人样”。
但别以为回老家就躺平了。上周五晚上十一点,我正窝在沙发上刷《三体》,手机突然“叮”一声——客户群里炸了:
“张工,我们新上线的后台管理系统被扫出未授权访问漏洞!测试说连 /admin/user/list 都能直接看,这要是被黑客利用,整个用户数据库都得裸奔啊!”
我心头一紧,赶紧打开电脑。果然,这个项目为了赶工期,安全模块直接用最简单的拦截器硬编码写了几行权限判断。结果呢?漏得跟筛子一样。
那一刻我真的想骂自己:35岁了,还犯这种低级错误?
事情得从三个月前说起
当时我接了个外包产品,帮一家本地电商公司搭个内部运营平台。需求很简单:管理员、运营、客服三个角色,各自能看到不同的菜单和数据。老板拍着我肩膀说:“老张,你经验丰富,两周搞定就行,预算3万。”
我心想,不就是个权限控制嘛,Spring Boot + MyBatis,搞个拦截器,if-else 一通判断,分分钟的事儿。结果第一版上线后,测试小妹(对,他们公司就一个兼职测试)发来截图:她用客服账号登录,居然能删商品!
我当场脸就红了。不是技术不行,是太飘了。总觉得自己写了十年代码,这种“基础活”闭着眼都能干。可现实狠狠抽了我一耳光。
那几天我失眠了。不是怕赔钱,是怕砸了口碑。在老家做自由职业,全靠熟人推荐。万一这事传出去,“老张连权限都搞不定”,以后谁还敢找我?
老婆看我愁得吃不下饭,劝我说:“要不咱不接这种活了?你不是还有那个远程全职岗吗?”
我说:“不行,这关必须过。不然我这十年代码白写了。”
痛定思痛,重拾 Spring Security
周六一早,我泡了壶浓茶,把 Spring Security 官方文档翻了个底朝天。以前总觉得它“太重”、“配置复杂”,不如手写拦截器灵活。但现在明白了:安全不是功能,是底线。你自己造轮子,轮子没造好,车翻了,乘客(用户数据)就完了。
我决定用最标准的方式重构。目标很明确:快速搭建一个可靠、可扩展、符合行业实践的安全认证系统。
第一步:引入依赖,告别“裸奔”
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
加完这一行,重启应用——所有接口都被拦住了!默认用户名是 user,密码在启动日志里打印出来。虽然丑,但至少不会裸奔了。
第二步:自定义用户认证逻辑
我们的用户存在数据库里,得对接自己的用户表。我建了个 UserDetailsService 实现类:
@Service
public class CustomUserDetailsService 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("用户不存在");
}
// 构造 Spring Security 的 UserDetails 对象
return org.springframework.security.core.userdetails.User
.builder()
.username(user.getUsername())
.password(user.getPassword()) // 注意:密码要加密存储!
.roles(user.getRole()) // 比如 "ADMIN", "OPERATOR"
.build();
}
}
这里有个坑:密码必须加密!我一开始图省事存明文,结果被 SonarQube 扫出高危漏洞。赶紧换成 BCrypt:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
注册时加密,登录时自动比对——Spring Security 都帮你做好了,何必自己造轮子?
第三步:配置权限规则——资源访问控制
这才是核心!我们要保护的是“资源”(比如 /admin/** 是管理员资源,/ops/** 是运营资源)。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll() // 公开资源
.requestMatchers("/admin/**").hasRole("ADMIN") // 管理员资源
.requestMatchers("/ops/**").hasAnyRole("ADMIN", "OPERATOR") // 运营资源
.anyRequest().authenticated() // 其他都要登录
)
.formLogin(login -> login
.loginPage("/login") // 自定义登录页
.defaultSuccessUrl("/home")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
}
看明白了吗?资源路径 + 角色 = 访问控制。清晰、声明式、无侵入。再也不用手动写 if (user.getRole().equals("ADMIN")) 了。
第四步:前后端分离?加个 JWT
客户后来要求做成前后端分离架构。那表单登录就不合适了,得上 JWT。
我在登录成功后生成 token:
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 验证用户名密码(Spring Security 会自动处理)
Authentication auth = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
// 生成 JWT
String token = jwtUtil.generateToken(auth.getName());
return ResponseEntity.ok(new JwtResponse(token));
}
然后写个过滤器,每次请求校验 token:
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
// 设置 SecurityContext
UsernamePasswordAuthenticationToken auth = ...;
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
最后在 SecurityConfig 里加进去:
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
搞定!现在前端每次请求带 Authorization: Bearer <token>,后端自动鉴权。
面试题挑战:为什么不用 Shiro?
重构完第二天,我跟一个刚跳槽的朋友视频聊天。他问我:“老张,你怎么不用 Apache Shiro?听说更轻量。”
我笑了:“Shiro 确实简单,但 Spring Security 是 Spring 家族亲儿子,生态、社区、集成度完全不是一个量级。而且——”我顿了顿,“面试官最爱问 Spring Security。”
他说:“真的?”
我说:“上周我帮一个学员改简历,他面阿里,二面就被问:‘Spring Security 的过滤器链顺序能说说吗?’、‘如何防止 CSRF 攻击?’、‘Remember-Me 机制原理?’——这些题,你光会用可答不上来。”
其实啊,掌握 Spring Security 不只是为了做产品,更是为了应对“面试题挑战”。大厂考它,是因为它代表了你对“安全”这件事的理解深度。你能手写拦截器,只能说明你会写代码;你能用好 Spring Security,说明你有工程化思维。
回头看:35岁,依然在路上
现在这个系统已经稳定运行两个月了。客户老板逢人就说:“还是老张靠谱!”——虽然我知道,我只是把该做的事做对了而已。
回老家这一年,我最大的感悟是:年龄不是问题,认知才是。35岁怎么了?只要愿意学,Spring Security 一样能啃下来。反而因为经历过生产事故,更懂得“稳”字有多重要。
有时候我会想,如果当初在大厂时多花点时间研究这些基础框架,是不是就不会踩这些坑?但转念一想,没有踩坑的程序员,永远长不大。
所以,如果你也在做类似的产品,别图快。花两天时间把 Spring Security 搭扎实,比后期修漏洞省十倍力气。
最后几句真心话
- 安全不是附加功能,是系统基石。别等被黑了才后悔。
- 别鄙视“基础技术”。Spring Security 看似老派,但它是经过千万级流量验证的。
- 35岁不是终点,是重新理解技术的起点。我现在写代码,比25岁时更慢,但更稳。
对了,上周我老婆看我加班到九点,有点担心:“又遇到难题了?”
我说:“没有,就是在给新项目搭安全框架。”
她点点头:“那你慢慢弄,饭给你留着。”
那一刻,我觉得值了。省下的房租、陪伴家人的时光、还有那份“把事情做对”的踏实感——这些,才是真正的“产品价值”。
共勉,各位还在一线战斗的老伙计们。

评论 0