从零快速搭建 Spring Security 安全认证系统:我的真实项目实战经验
大家好,我是一名在一家中型互联网公司工作的后端开发工程师。我们公司做的是 ToB 的 SaaS 平台业务,用户类型包括普通员工、管理员以及合作方的接入方。作为一个核心模块,安全认证是平台的基础需求之一。
在我参与这个 SaaS 平台重构的时候,身份认证模块还是基于传统的 Session + Cookie 方案,并且和业务代码耦合得比较严重,维护起来很不方便。当时正好有机会引入更现代的认证方式,比如 JWT 和 OAuth2,同时也想尝试使用 Spring Security 来统一整个系统的权限控制流程。
这篇文章我会结合自己实际接手的项目,分享一下我是怎么一步一步用 Spring Security 快速搭建一个灵活、可扩展的安全认证系统,并在这个过程中解决遇到的各种问题。希望我的经历能给刚入门或正在学习 Spring Security 的朋友们一些启发。
开篇背景:为什么选择 Spring Security?

我们的平台是典型的 Java Web 架构,前后端分离,前端为 Vue3,后端为 Spring Boot。早期的身份认证逻辑写在 Controller 层,通过自定义注解拦截请求判断是否登录,这种做法随着业务增长逐渐暴露出以下几个问题:
- 登录状态失效没有统一出口,容易产生 bug;
- 用户角色权限处理分散,不同接口各自校验,出错率高;
- 接口权限难以复用,修改权限规则需要大量改代码;
- 对接第三方系统时缺乏标准化的安全协议支持(如 OAuth2);
这些问题累积到一定程度后,项目组决定重构安全认证体系,目标是:
- 实现统一的认证流程;
- 支持 JWT;
- 模块化权限管理,便于未来扩展;
- 提升整体架构的健壮性和安全性。
Spring Security 就是在这个时候进入视野的。它虽然是出了名的复杂,但一旦理解清楚了它的过滤器链机制、AuthenticationProvider 等设计思想,你会发现它真的很强大。
遇到的挑战:Spring Security 初期上手难


刚开始接触 Spring Security 的时候,确实被它那一整套“术语”搞晕过一阵子。
比如:
FilterChainProxy是什么?SecurityProperties和手动配置冲突怎么办?- 自定义登录验证流程该怎么做?
- JWT 放哪里?如何整合进 Spring Security 的体系?
我当时在网上查了很多资料,但很多教程只是讲“跑通例子”,而没有深入讲解原理和实际部署注意事项,导致在实际项目中经常踩坑。最典型的一个问题是:登录成功后返回 JWT,但后续带 Token 请求无法通过安全层验证。
这其实是对 Spring Security 的验证流程理解不深造成的。
解决方案:一步步实现安全认证系统
为了把事情说清楚,我打算按照项目实践的顺序来介绍我的实现思路,涵盖以下关键点:
1. 项目结构与依赖配置
项目使用 Spring Boot 2.7.x,Spring Security 5.6.x(版本之间兼容性很重要),加上 JWT 工具包 jjwt。
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
然后是一个简单的登录接口:
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginDTO loginDTO) {
// 这里先模拟用户校验逻辑
String token = jwtUtils.generateToken(loginDTO.getUsername());
return ResponseEntity.ok(token);
}
此时的问题是——生成了 token,但后续请求怎么让 Spring Security 认出来?
2. 自定义 JWT 的过滤器与解析逻辑
我们需要让 Spring Security 在收到每个请求时自动检测 header 中的 token,提取用户名并构建 Authentication 对象。
于是,我创建了一个 JwtRequestFilter 类,继承 OncePerRequestFilter,重写了其中的 doFilterInternal 方法:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwtToken = authorizationHeader.substring(7);
try {
username = jwtUtils.extractUsername(jwtToken);
} catch (JwtException e) {
logger.warn("Invalid JWT token: {}", e.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
return;
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
这段代码的作用就是:如果请求头里有 token,就把它提取出来,构造出一个匿名的 Authentication 对象,交给 Spring Security 管理。这样后面的权限判断就能正常执行了。
3. 配置 Spring Security 的安全策略
接下来就是在 SecurityConfig 类里配置过滤器链路和放行策略:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtRequestFilter jwtRequestFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.anyRequest().authenticated();
return http.build();
}
}
这里最关键的就是 addFilterBefore(...) 把我们自己的 JWT 过滤器加进去,并让它在 Spring Security 的默认认证流程之前运行。
4. 整合角色权限校验(RBAC)
前面提到的权限散落在各接口中是个大问题,我们决定采用标准的 RBAC 模型:
- 用户 User 可以拥有多个 Role;
- Role 可以绑定一组 Permission;
- 在 Controller 或 Service 层通过
@PreAuthorize("hasAuthority('PERMISSION_NAME')")注解进行权限控制。
为了实现这一点,在 Spring Security 中我们需要开启方法级权限控制:
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig { ... }
同时,我们在数据库中建立三张表:
usersrolespermissions- 关联表
user_roles,role_permissions
当用户登录后,我们在生成 JWT 的时候也一并将权限信息放入 claims:
{
"username": "zhangsan",
"permissions": ["view_user", "edit_order"]
}
在过滤器中解析完 token 后,就可以根据这些权限生成对应的 SimpleGrantedAuthority,最终传给 Spring Security 使用。
List<GrantedAuthority> authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username, null, authorities);
这样就可以完美支持基于权限的访问控制了。
成果展示:认证系统上线后的效果

经过大约两周的时间,我们完成了整个认证模块的重构,取得了不错的成果:
- 原先分散在各个服务中的权限校验逻辑全部收拢;
- 新增权限只需添加一行
@PreAuthorize("xxx")注解; - 所有接口默认都需要认证,未授权请求会直接返回 403;
- JWT 过期时间可配,支持刷新 token 机制;
- 日志清晰,方便调试和追踪;
- 第三方系统接入时也可通过集成 OAuth2 快速完成授权对接。
这套认证系统后来还支撑了多租户改造、SSO 单点登录等多个新功能,成为整个平台安全体系的核心基础。
经验总结:几点建议送给后来者
虽然 Spring Security 功能强大,但它并不是万能药,也不是一开始就要用全量功能。我在使用过程中总结了一些心得,供大家参考:
1. 按需配置,别一上来就把 Security 弄得太复杂
很多人一上来就想着要配置 CAS、OAuth2、SAML……其实绝大多数业务初期只需要做好“登录 → 鉴权 → 权限控制”这一条线就够了。
2. 理解过滤器链机制,是掌握 Spring Security 的关键
不要只停留在“复制粘贴”示例代码层面,一定要去理解请求是怎么穿过一个个 filter 最终走到 controller 的。建议看一遍源码,尤其是 BasicAuthenticationFilter, UsernamePasswordAuthenticationFilter 这些默认组件。
3. 注意性能,尤其是在高并发场景下
我们在压测时发现,某些 filter 如果每次都去数据库查权限,会有明显的延迟。后来我们将用户权限信息缓存在 Redis 中,并设置了合理的 TTL,大大提升了性能。
4. 日志 + 调试工具是排查安全问题的利器
特别是在生产环境,有时候某个 API 无缘无故报 403,这时候就需要启用 debug 日志来查看是哪一层 filter 阻止了请求。
5. 安全不能只靠框架,也要结合运维手段
比如定期更新密钥、设置合适的 JWT 过期时间、限制登录失败次数等,都是必不可少的安全措施。我们配合 Nginx 设置了 IP 限流,在防止暴力破解方面起了很大作用。
写在最后:安全是一场持久战
做开发这几年,我越来越觉得,安全从来不是“一次性工程”,而是一场持续演进的过程。Spring Security 固然是一个非常棒的工具,但只有真正理解它的设计哲学,才能用得好,用得稳。
如果你也是刚开始接触安全认证这块内容,不妨从一个小项目入手,慢慢把 Spring Security 加入你的知识库中。它可能有点难,但我相信你一旦掌握了,就会爱上它那种“一切皆可插拔”的设计风格。
希望这篇来自我亲身经历的技术分享能对你有所帮助,如果你有任何疑问或建议,欢迎留言交流!

评论 0