用 Spring Security 快速搭建安全认证系统:我在项目中踩过的那些坑
引言:为什么要讲这个话题?

去年我负责了一个企业内部管理系统开发项目,需要为多个业务模块提供统一的用户登录、权限控制和访问管理。项目初期,我们考虑过自研一套权限系统,但权衡下来发现,维护成本高、漏洞风险大。最终我们决定采用 Spring Security 这个成熟的框架来构建系统的安全认证模块。
虽然之前对 Spring Security 有过一定了解,但在实际项目落地时还是遇到了不少挑战:从简单的登录功能到复杂的 RBAC 模型实现,再到后期的性能优化,整个过程让我对这个框架有了更深层次的理解。今天我想结合这个项目的实战经验,分享下如何快速上手并正确使用 Spring Security 来搭建一个安全可靠的认证系统。
背景与问题:系统需要什么样的认证能力?

我们的项目是一个典型的 B/S 架构管理系统,面向公司员工使用,涉及 HR、财务、采购等多个模块。不同角色之间的权限差异极大,比如普通员工只能查看自己的考勤记录,HR 可以审批流程,而部门主管有更高的审批权限。
一开始我们只用了最基础的表单登录,后来随着功能迭代,暴露的问题也越来越多:
- 登录方式单一:不支持 OAuth2 或 Token 认证。
- 权限粒度过粗:方法级别的权限控制缺失。
- 安全性不足:没有 CSRF 防御、Session 管理混乱。
- 架构设计不合理:安全配置散落在各个模块,难以维护。
这时候我们必须重新梳理权限模型,并选择一个足够灵活、可扩展的安全框架 —— Spring Security 成为了首选。
解决思路:为什么是 Spring Security?
选择 Spring Security 的原因有以下几点:
- 生态成熟:它几乎成为了 Java 后端的标准安全框架,社区活跃、文档齐全。
- 插拔式结构:很多功能可以通过配置开启,不需要重复造轮子。
- 高度可定制化:从 Authentication 到 Filter Chain,都提供了丰富的扩展点。
- 集成能力强大:支持 JWT、OAuth2、LDAP、SAML 等多种协议和身份源接入。
- 与 Spring Boot 天然契合:开箱即用,启动迅速。
在设计整体架构时,我们明确了几个目标:
- 支持多租户(虽然是单系统,但要预留未来拆分接口)。
- 支持前后端分离模式下的 Token 认证。
- 实现基于角色和资源细粒度的权限控制(RBAC)。
- 提供 Session 和 Token 的混合管理机制。
关键实现步骤与代码示例
1. 基础依赖引入(Maven)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 如果需要 JWT 支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.11.5</version>
</dependency>
2. 核心配置类 SecurityConfig
我们在 SecurityConfig 中定义了整体的安全策略,包括:
- 登录逻辑接管
- 请求路径的权限控制
- 自定义过滤器链注入
- CORS 配置支持前后端分离
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.build();
}
// 密码加密 Bean
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. 自定义 JWT 过滤器
我们使用 JWT 作为 Token 传输机制,在每次请求头部提取 Token 并进行解析校验,然后设置用户信息到上下文:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromHeader(request);
if (token != null && JwtUtils.validateToken(token)) {
String username = JwtUtils.extractUsername(token);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username, null, getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private Collection<? extends GrantedAuthority> getAuthorities() {
// 可以从数据库或缓存加载权限信息
return AuthorityUtils.createAuthorityList("ROLE_USER", "PERMISSION_READ");
}
}
4. 数据库设计与权限模型
我们采用了经典的 RBAC 模型,主要表结构如下:
- user:存储用户基本信息,如用户名、密码(加密)、状态等。
- role:角色表,用于划分权限集合。
- permission:具体的权限项,例如"hr:attendance:view"。
- user_role:用户与角色的关系。
- role_permission:角色与权限的映射关系。
通过这套模型,我们可以实现灵活的权限控制,例如:
@PreAuthorize("hasPermission(#userId, 'USER', 'VIEW')")
@GetMapping("/user/{userId}")
public UserResponse getUser(@PathVariable Long userId) {
// ...
}
踩坑经历与解决之道
在项目推进过程中,我们也遇到过不少“坑”,这里分享几个印象比较深的。
问题一:CSRF 和 Cors 配置冲突
一开始我们没有关闭 CSRF,导致 POST 请求总是被拦截。原因是前端发来的请求头缺少相应的 token,后来才意识到我们是在前后端分离场景下运行,需要显式地关闭 CSRF:
.csrf().disable();
同时还需要配置 CORS:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
问题二:Filter 执行顺序错误导致无法拦截 Token
我们最早自定义的 JWT 过滤器放错位置了,导致 Token 校验没生效。正确的做法是:
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
一定要插到默认登录验证 Filter 的前面,否则后面再想加就无效了。
问题三:并发登录导致 Session 冲突
我们早期尝试用 Session 存储用户信息,结果出现 A 用户登录后,B 用户也能看到 A 的数据。排查后发现是未正确隔离线程上下文,最后改成了无状态的 Token 模式才解决问题。
最终效果与收益总结
经过两个月的安全重构后,整个系统的认证模块稳定了很多:
- 登录速度提升约 30%,因为去掉了 Session 持久化操作。
- 不同角色的权限颗粒度更加精细,可以精确到按钮级别。
- 安全性大大增强:XSS、CSRF、Session Fixation 等问题基本杜绝。
- 接口调用日志清晰,便于后期审计与运维。
现在每次新模块上线,我们只需要按需求配置一下权限表达式就可以了,大大节省了开发时间。
经验总结与建议
如果你正准备用 Spring Security 搭建安全认证系统,这里是我的一些小建议:
- 不要一开始就想着自己写过滤器,先理解 Spring Security 的默认流程,再根据需要进行定制。
- 把权限模型放在第一位,搞清楚你要的是 ACL、RBAC,还是 ABAC。
- 安全层尽量解耦于业务逻辑,避免将权限判断硬编码在 Controller 或 Service 层。
- 对于前后端分离系统,推荐使用 Token + 自定义 Filter 的方式,避免 Session 带来的复杂性。
- 合理利用 Spring Expression Language(SpEL)来做动态权限判断,提高灵活性。
- 定期更新依赖版本,特别是 Spring Security 这种高频更新的包,注意修复已知漏洞。
结语:技术人的成长不止于代码本身

回想起来,这个项目的认证重构不仅提升了系统的安全性,更重要的是让我对“权限”这件事有了更深的理解:它不仅是登录登出,更是贯穿整个系统生命周期的重要组成部分。
很多时候我们以为只是写个登录接口的事儿,但实际上背后可能藏着成千上万条规则和异常边界情况。Spring Security 给我们提供了一个很好的起点,关键还在于怎么把它用好。
希望这篇文章能对你有所帮助,少走一些弯路。毕竟在这个世界上,比写出代码更难的,是写出真正靠谱的代码。

评论 0