用 Spring Security 快速搭建安全认证系统:从需求到上线的实战经验分享
大家好,我是阿亮。最近在一个项目中遇到了一个典型的后端权限和认证问题,我负责的是用 Spring Boot + Spring Security 搭建一套完整的认证授权体系。说实话,刚开始我觉得这挺简单,不就是加个 @EnableWebSecurity 然后再写几个过滤器嘛?可真做起来才发现,理想很丰满,现实很骨感。
今天我就来聊聊我们是怎么一步步搞定这件事的,包括遇到的问题、踩过的坑,以及最终的效果。希望能帮助大家在实际项目中少走弯路。
背景与挑战:一个典型的 SaaS 系统安全需求

项目的背景是一个企业级的 SaaS 系统,涉及多个用户角色(比如管理员、普通用户、访客),不同角色访问的资源不同,并且需要支持 OAuth2 的第三方登录,以及基于 Token 的 API 访问鉴权。
我们的初步目标:
- 支持用户名密码登录
- 支持基于 JWT 的无状态认证
- 实现角色控制(RBAC)
- 整合第三方登录(如微信、钉钉)
- 统一处理未授权访问、超时登出等场景
- 安全性要符合基本的防御标准(防 CSRF、XSS、SQL 注入等)
遇到的主要问题:
- Spring Security 的配置复杂,尤其是整合 JWT 后,很多地方都需要自定义。
- 权限模型设计不合理,导致后期改动成本高。
- Token 的刷新机制没考虑周全,出现部分接口反复返回 401。
- OAuth2 登录流程一开始走了弯路,最后才搞清楚是该自己实现还是用框架默认逻辑。
- 测试环境表现正常,生产部署却频频报错,查了半天发现是配置文件加载顺序的问题。
解决方案:Spring Security + JWT + OAuth2 的组合拳

经过一轮调研和团队讨论,我们决定采用以下技术栈组合:
- Spring Boot 2.7
- Spring Security 5.7
- JWT(使用 Java-JWT 库)
- 数据库:MySQL + MyBatis Plus
- 前端交互:Vue + Axios
整体架构上,我们采用了经典的分层结构:Controller -> Service -> Mapper,并在 Controller 层之上增加了 Security 过滤链,负责认证和鉴权。
架构图简述(伪代码版)

[Client] → [Nginx/LB] → [Spring Boot]
│
┌─────▼──────┐
│ Spring │
│ Security │
└─────┬──────┘
│
┌─────────────┴──────────────┐
▼ ▼
UsernamePasswordAuthFilter JwtAuthenticationFilter
│ │
↓ 用户名密码登录 ↓ Token 验证
↓ ↓
UserDetailsService TokenStore / Redis
↓
权限验证(Role-based)
↓
AccessDecisionManager
整个流程清晰易扩展。下面详细说一下实现的关键点。
实战编码:关键配置和代码片段

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>
如果使用 OAuth2,还需要加入 spring-security-oauth2-resource-server。
2. 自定义 JWT 工具类
我们封装了一个 JwtUtils,用来生成和解析 token:
public class JwtUtils {
private static final String SECRET_KEY = "your-secret-key";
private static final long EXPIRATION = 86400000; // 24h
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public static String parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
这里只是简单示例,实际项目中建议加上签发时间、刷新令牌等字段。
3. 编写自定义 JWT 过滤器
我们需要编写一个继承 OncePerRequestFilter 的类,用于拦截请求并验证 token:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && validateToken(token)) {
String username = JwtUtils.parseToken(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 (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private boolean validateToken(String token) {
try {
JwtUtils.parseToken(token);
return true;
} catch (JwtException e) {
return false;
}
}
}
4. 配置 Spring Security
接下来是最重要的 SecurityConfig 类:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin().disable()
.httpBasic().disable();
return http.build();
}
}
可以看到,我们在这里做了几件事:
- 关闭 csrf,因为我们是前后端分离架构
- 使用无状态 session,配合 JWT
- 加入自定义的 token 校验过滤器
- 设置
/login免权限访问 - 角色为 ADMIN 可以访问
/api/admin/** - 禁用了表单登录和 Basic Auth,统一走 Token 鉴权
5. 接口设计:如何优雅地处理登录
为了简化流程,我们提供了一个通用的登录接口 /login:
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequestDto requestDto) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(), requestDto.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = JwtUtils.generateToken(requestDto.getUsername());
return ResponseEntity.ok().header("Authorization", "Bearer " + token).build();
}
这里的 authenticationManager 是通过注入获取的,默认已经自动配置好了基于用户名密码的认证流程。
踩过哪些坑?
坑一:权限表达式写错了,导致所有接口都可以访问
最开始我在配置 .hasAuthority() 的时候,错误地写成了 .hasRole("admin"),但数据库里保存的角色是 "ADMIN"。结果就是永远进不去 admin 页面,后来才知道 hasRole() 默认会自动添加 "ROLE_" 前缀,所以应该是 .hasRole("ADMIN") 才对。
坑二:Token 刷新机制没做好,出现频繁掉线
初期我们用本地内存存 Token,导致服务重启就失效;后来改用 Redis 存储,但在集群环境下没有设置一致性过期策略,结果个别节点提前失效。解决办法是在每次请求都更新 Redis key 的 TTL,保持同步。
坑三:跨域请求被拦截了
前端 Vue 请求后端时,总是提示不允许的头部或方法。后来才发现是 CORS 的问题,在 Spring Security 中如果不显式放开 OPTIONS 请求,会被默认拦截。解决方案是在 SecurityConfig 中加上:
.cors().configurationSource(corsConfigurationSource())
并配置允许的域名、头信息和方法。
上线后的效果与优化建议
整个系统上线之后,性能和安全性表现都不错,QPS 在高峰期能维持在 3K 左右。我们还结合 Nginx 和 Redis 做了集群部署和 Session 同步。
不过随着用户量上涨,我们逐步做了以下几个优化:
- 增加 Token 黑名单机制:防止旧 Token 被盗用
- 引入分布式缓存管理:Redis 分布式锁控制并发操作
- 日志监控接入 ELK:追踪异常登录行为
- 定期更换密钥:提升长期安全性
- 接口粒度更细的权限控制:从接口级别细化到方法或字段级别
给开发者的几点建议
作为一线开发人员,我想分享一些亲身体会给正在看这篇文章的你:
- 不要死记硬背 Spring Security 配置,重点理解 Filter Chain 的执行流程。
- 安全设计要有前瞻性,别等到上线再补漏洞,代价太高。
- 多动手实践,少看教程照搬,否则遇到特殊情况就会束手无策。
- 结合项目实际情况选型,不是所有项目都需要 OAuth2,也不是所有都要 RBAC,按需定制最好。
- 安全不能只靠框架,应用层也要有意识,比如日志脱敏、输入校验、SQL 参数化处理等等。
总结
Spring Security 功能强大,但也“有点脾气”。要想用得好,不仅要掌握它的原理,还要结合具体业务场景灵活调整。尤其是在实际项目中,往往不只是简单的认证授权,还需要考虑性能、扩展性和安全性。
希望通过这篇实操经验的分享,能帮你避开那些“看着简单,用着难”的坑,快速搭起自己的安全认证系统。如果你也在用 Spring Security 或者准备开始接触它,欢迎留言交流,我们一起成长。
作者简介
阿亮,一名热爱写代码、热衷于分享经验的后端开发者,目前专注于企业级系统的架构设计和安全体系建设。业余喜欢写博客,偶尔也写点开源项目。欢迎关注我的 GitHub 和知乎专栏。

评论 0