用 Spring Security 快速搭建安全认证系统:我的实战经验分享
背景介绍

去年我们团队接了个新项目,需要为一个大型的 SaaS 平台开发一套用户权限体系。这个平台不仅要支持内部员工登录管理后台,还要对外开放 API 接口供客户调用,并且涉及到数据敏感性较高,对安全性要求很高。
最开始我们考虑自己从零设计整套认证机制,比如基于 Session、JWT 或者 OAuth2 自行实现。但越深入调研发现,这些方案在实际场景中都有不少“坑”,特别是在用户角色划分、接口鉴权策略、多租户支持等方面,自研的成本和复杂度远超预期。
于是我们决定回归到 Java 开发界耳熟能详的安全框架 —— Spring Security。作为 Spring 家族的核心成员,它虽然强大但也因为学习曲线陡峭,很多人望而却步。不过,在经历过这次项目之后,我反而觉得它是一个非常值得投入精力去掌握的工具。
今天想结合这次项目经历,分享一下我是如何快速上手并成功落地 Spring Security 的整个过程。
遇到的问题与挑战

我们遇到的主要问题包括:
统一接入标准难
平台前后端分离严重,有些接口面向浏览器用户,有些面向第三方 API 用户(使用 Token),还有部分用于设备端(如 IoT)。希望有一套灵活的配置方式来兼容多种认证方式。权限控制粒度过细
每个模块有不同的角色权限,比如“管理员”可以访问所有资源,“编辑”只能读写部分内容。这种细粒度控制如果自己实现会非常麻烦。性能压力大
系统预计并发请求高达数千 QPS,传统基于 Session 的验证方式可能扛不住高负载。运维部署困难
因为我们是分布式架构,部署节点很多,认证状态需要一致性,所以还必须支持集中式授权,避免 Session 不同步的问题。迁移成本顾虑
已有旧系统使用了 JWT + Shiro 的组合,担心替换框架会带来大量改动。
为什么选择 Spring Security?
技术选型对比小结
我们当时也考察了一些可替代方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| Spring Security | 功能完整、集成简便、扩展性强 | 上手有一定学习成本 |
| Apache Shiro | 简洁易用、文档清晰 | 社区活跃度不如 Spring,功能偏基础 |
| Auth0 / Keycloak (第三方服务) | 架设快,省心省力 | 成本高,定制化受限,依赖外部 |
| 自建认证中心 | 完全可控 | 实现难度大,维护成本高 |
最终我们还是选择了 Spring Security,因为它:
- 支持 JWT、OAuth2、Session、Basic Auth 等主流认证方式;
- 提供 RBAC 权限模型,细粒度控制轻松搞定;
- 可以通过插拔式组件适配不同存储方式(数据库、LDAP、内存等);
- 和 Spring Boot 天然兼容,集成简单;
- 社区活跃、文档丰富,生态成熟。
解决思路与整体设计
在正式开工之前,我花了一个多天理清整个认证流程的设计逻辑,大致分为以下几个关键步骤:

1. 认证流程图解构(简化版)
用户 -> 登录接口 -> 验证用户名密码 -> 发放 Token
↓
(Header带入Token) → 请求其它接口 → FilterChain 进行拦截
↓
Spring Security验证签名有效性
↓
是否满足接口所需权限?
↓
是 → 继续处理请求
否 → 返回 403 Forbidden
2. 整体结构拆分(模块职责明确)
我们将整个安全模块拆分为几个核心组件:
- AuthenticationEntryPoint: 未登录时的响应入口(返回 JSON 格式的错误提示)
- AccessDeniedHandler: 无权限时的处理器
- JwtFilter: 对每个请求进行 Token 校验和封装 Authentication
- UserDetailsService: 加载用户信息(来自数据库或缓存)
- SecurityProperties: 配置相关参数(如加密密钥、有效期等)
这样做既保持了解耦,又便于后期扩展。
关键代码实现与示例
下面展示一些关键部分的代码,以便读者更直观感受其实现方式。
🧱 1. 添加依赖(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
🪄 2. 自定义 JwtToken 工具类
public class JwtUtils {
private static final String SECRET = "your_very_secret_key";
private static final long EXPIRATION = 86400000; // 24 小时
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public static String extractUsername(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject();
}
public static boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}

🔍 3. 实现过滤器链(JwtFilter)
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && JwtUtils.validateToken(token)) {
String username = JwtUtils.extractUsername(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
🛡️ 4. 安全配置类(SecurityConfig)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtFilter jwtFilter;
@Autowired
private AuthEntryPoint authEntryPoint;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authEntryPoint)
.accessDeniedHandler(new CustomAccessDeniedHandler());
http.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
return http.build();
}
// 密码编码器使用 BCrypt
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
开发过程中踩过哪些坑?
1. OncePerRequestFilter 被绕过?
一开始我在调试时发现 JwtFilter 好像没生效,后来才意识到是因为某些静态资源路径没有正确注册。解决方案是:
.antMatchers("/static/**", "/favicon.ico").permitAll()
或者将 .anyRequest().authenticated() 移到最后。
2. Token 无法在多个节点间共享?
原本我们的微服务集群部署在不同的服务器上,由于密钥不同导致各个节点无法解析其他机器生成的 Token,解决方法是在配置中心统一设置相同的 SECRET_KEY。
3. 使用 @PreAuthorize 注解后报错?
Spring Security 默认并不开启方法级注解,需要手动打开:
@EnableGlobalMethodSecurity(prePostEnabled = true)
4. 单元测试怎么模拟登录?
测试 Controller 时经常要伪造一个已登录的用户,可以直接使用如下代码:
@BeforeEach
void setup() {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken("test_user", null, AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
实施后的效果与收益
整个项目上线之后,我们看到明显的变化:
✅ 接口访问效率提升:Stateless 的 JWT 机制减少了数据库查询频率;
✅ 安全性显著增强:所有的资源访问都受控于 Spring Security 的规则引擎;
✅ 扩展灵活:当我们要支持新的认证方式(如微信扫码登录)时,只需要添加一个对应的 Filter 即可;
✅ 权限管理方便:RBAC 的支持使得我们在运营后台做用户管理变得非常直观。
而且后续在做多租户隔离和审计日志的时候,也因为我们这套完整的认证体系打好了基础,节省了大量的时间。
我的一些心得体会和建议
作为一名后端开发者,我认为掌握 Spring Security 是一件很有必要的事。以下是我在此次项目中的几点建议:
不要被复杂的官方文档吓退
Spring Security 官方文档确实又厚又长,但只要理解其核心机制(Filter、Authentication、Provider 等),就能逐步建立起全局视角。先跑通流程再优化细节
建议初学阶段先搞清楚认证流程,再一步步加上诸如角色控制、动态权限之类的功能,否则很容易晕头转向。重视异常处理机制
前端对接时,一定要统一好错误格式,而不是让 Spring Security 默认返回 HTML 页面,这样前端会很痛苦。合理利用社区轮子
如:Spring Security + OAuth2 结合使用的场景非常多,GitHub 上有很多高质量的 starter 包可以直接拿来用。生产环境别忽略日志和监控
对登录失败、访问拒绝等操作做好埋点,有助于及时发现潜在攻击行为。
总结
总的来说,Spring Security 虽然入门门槛稍高,但在真正的工程项目中,一旦熟悉它的套路,你会发现它几乎是无可替代的安全利器。不仅能满足基本认证,还能通过良好的扩展性应对各种复杂权限场景。
如果你也在寻找一个既能支撑业务发展,又能贴合现有技术栈的安全框架,不妨试试看 Spring Security。当然,也可以参考我们这次的做法,用 JWT + Spring Security 的方式进行轻量改造,兼顾灵活性与性能。
最后想说:安全这件事,从来都不是加一串中间件那么简单。只有不断打磨流程、优化体验,才能做到真正意义上的“放心交付”。
如有任何疑问或不同看法,欢迎留言交流,我们一起成长!
📌 想获得更多干货内容?欢迎关注我的技术博客或公众号,一起探索更多后端开发的最佳实践!

评论 0