用 Spring Security 快速搭建一个安全认证系统:我的实战经验分享
引言:为什么会选择写这篇?

我是一名后端开发工程师,在工作中经常需要为不同的业务系统搭建基础的安全认证模块。有一次我们团队接手了一个新的项目,客户希望在两周内交付一个后台管理系统的基本框架,包括登录、权限控制、角色管理等核心功能。
为了快速实现这些需求,我们决定使用 Spring Security 来构建系统的安全认证机制。但说起来容易,做起来才发现问题不少:如何高效地整合 JWT?数据库表怎么设计?Spring Security 的配置方式又改了?……这些问题一个个冒出来,让我意识到虽然 Spring Security 很强大,但如果不对它有清晰的理解,很容易掉进坑里。
今天我想结合这个项目经历,从实际开发的角度出发,和大家分享一下如何用 Spring Security 快速搭建一个安全认证系统,并谈谈我在过程中的一些心得体会和踩过的坑。
项目背景与挑战:两周搭好权限体系

我们要做的项目是一个中小型企业的内部管理系统,主要面向的用户群体是公司的员工。系统本身需要以下几个核心功能:
- 用户登录
- 角色权限划分(比如管理员、运营、普通员工)
- 基于角色的接口访问控制
- 支持 Token 登录和刷新机制
- 对接已有的 MySQL 数据库
时间紧迫,资源有限,我们必须在有限的时间内完成权限体系的设计和落地。当时我们在选型时也讨论过其他方案,比如 Shiro、Auth0 这类现成的认证服务,但由于项目的私有化部署要求、灵活性以及后续可维护性等原因,最终还是决定采用 Spring Boot + Spring Security 搭建一套相对通用的认证授权系统。
不过真正开始开发的时候,我发现很多细节并没有想象中那么简单。
技术选型与整体架构

先来看看我们的技术栈:
- 后端:Java 17 + Spring Boot 3.1 + Spring Security 6.x
- 认证方式:JWT + 内存中的 In-Memory UserDetailsService(后期切换到数据库)
- 数据库:MySQL 8 + MyBatis Plus
- 前端:Vue 3(不涉及本文重点)
整个认证流程的大致思路如下:
- 用户通过
/login接口提交用户名密码; - 系统验证后生成包含用户信息和角色的 JWT;
- 后续请求需携带 Token,由拦截器解析并设置当前用户上下文;
- Spring Security 根据角色控制接口访问权限。
听起来是不是挺清晰的?但在具体落地的过程中,却遇到了一些让人头疼的问题。
实际遇到的问题与解决方案

问题一:Spring Security 配置方式变了怎么办?
以前我们习惯用 WebSecurityConfigurerAdapter 来重写配置方法,但从 Spring Security 6 开始,这个类已经被弃用了。我一开始还在按照之前的教程照搬,结果发现压根跑不起来。
解决办法也很简单:直接基于 Bean 的方式来配置过滤链和权限规则。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtService), UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyEndpoint().authenticated()
)
.build();
}
这种声明式的配置方式更直观,也更适合多环境配置。
问题二:JWT 令牌该怎么集成进 Spring Security?
我们一开始的想法很简单:只要解析 Token 设置 Authentication 就可以了。然而 Spring Security 并不会自动处理这一点,必须要自定义一个 Filter。
这里我们做了几个关键点:
- 自定义了一个
JwtAuthenticationFilter,继承OncePerRequestFilter,确保每个请求只执行一次。 - 使用 Redis 缓存 Token 的吊销状态,避免无效 Token 继续生效。
- 在登录成功后返回 Token 和 Refresh Token,设置合适的过期时间。
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtService.isValid(token)) {
Authentication auth = new UsernamePasswordAuthenticationToken(
jwtService.getUsernameFromToken(token),
null,
jwtService.getAuthoritiesFromToken(token)
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
小插曲:最开始没有加上 .setAuthentication(),导致后面的 @PreAuthorize("hasRole('ADMIN')") 注解根本不起作用。这事儿整整调试了一天才意识到是上下文没设置!
问题三:权限如何动态加载?
刚开始我们把所有用户角色写死在配置文件里,测试没问题,上线以后才发现用户量上去之后无法灵活调整权限。后来我们将用户权限数据从数据库中读取,并缓存在 Redis 中。
我们定义了一个 UserDetails 的扩展实现,里面包含了用户的角色列表:
public class CustomUserDetails implements UserDetails {
private final String username;
private final String password;
private final Collection<? extends GrantedAuthority> authorities;

// ...
}
在用户登录时,根据用户名从数据库查出该用户对应的所有角色权限,然后构造 Authority 列表放入 Token 中。
这样就可以做到权限的“动态”控制,只需要更新数据库里的角色权限映射即可,不需要重新发版或重启服务。
数据库设计的考虑
用户权限系统中最关键的部分就是数据库的设计。我们采用了以下几张核心表:
| 表名 | 描述 |
|---|---|
users |
存储用户基本信息 |
roles |
角色表 |
permissions |
权限表(如 read_user、write_order) |
user_roles |
用户与角色的关联表 |
role_permissions |
角色与权限的关联表 |
这样设计的好处是:
- 可以支持多角色分配给一个用户
- 角色权限松耦合,方便后期扩展
- 权限粒度细,适合 RBAC 模型
查询某用户权限时可以通过多个 JOIN 查询获取所有的 Permission 字符串,然后封装成 GrantedAuthority 提供给 Spring Security 使用。
性能优化和运维上的几点建议
随着用户增多,我们逐渐开始关注性能问题。特别是 Token 解析、用户权限加载、Redis 缓存命中率这些方面。
1. Token 解析耗时过多怎么办?
我们初期对 Token 的解析逻辑都放在拦截器中同步进行,当并发量上来之后,明显感觉请求延迟上升。
优化手段:
- 将 JWT 解析过程中的异常处理统一捕获,防止因格式错误导致的频繁 GC
- 加入本地缓存(例如 Caffeine),缓存最近半小时内的有效 Token 对应的用户信息和权限,降低解析开销
2. Redis 缓存失效怎么办?
我们使用 Redis 存储 Token 的黑名单(吊销状态)。最开始没有设置合适过期时间,导致某些 Token 被永久封禁了,影响体验。
优化措施:
- 设置 Token 的 TTL 与实际有效期一致
- 使用 Redis 的 Pipeline 机制批量操作吊销 Token
- 定期清理已过期的 Token 缓存条目
3. 日志监控怎么做?
权限相关的日志至关重要,我们通过 AOP 切面记录了以下信息:
- 登录失败次数超过限制自动锁定账户
- 用户每次操作的 IP、时间、请求路径、权限变化情况
这些日志通过 ELK 分析,帮助我们在生产环境中及时发现暴力破解或越权行为。
最终效果与收益
这套基于 Spring Security 构建的安全认证系统最终如期上线,并且具备了以下特点:
- 用户登录、角色权限动态管理
- JWT Token + Redis 刷新机制
- 多种登录失败策略(锁IP、账号锁定)
- 可扩展性强,后续接入 OAuth2 成本低
最重要的是,整套系统可以在 两天内快速复用 到新项目中,极大地提升了团队效率。
我的几点经验总结
如果你正准备用 Spring Security 搭建自己的认证系统,或者已经在路上遇到了困惑,下面是我的一些经验建议,希望能帮到你:
✅ 1. 不要迷信“全自动配置”
Spring Security 提供了强大的默认配置,但在企业级项目中往往需要定制化处理。比如默认的 Form 登录、默认的 AccessDeniedHandler,在前后端分离架构下基本不用。
所以别怕自己动手写 Filter 和 Handler,这才是真正可控的做法。
✅ 2. JWT 和 Session 各有利弊,选择要看场景
如果要做无状态认证,JWT 是很好的选择;但如果需要强一致性控制,Session + Cookie 更可靠。不要一味追求流行趋势。
✅ 3. 权限尽量早设计,否则后期难改
权限模型一旦确定就很难大改,特别是在权限字段已经在代码层面硬编码的情况下。建议尽早画清权限边界,合理划分 Role 和 Permission。
✅ 4. 测试用例一定要覆盖各种权限组合
我们曾经因为某个角色权限配置错误,导致管理员看不了用户列表。这类问题只能靠完善的单元测试和集成测试提前暴露。
✅ 5. 做好文档,便于交接和迭代
权限系统的复杂度常常让新人望而却步。记得写下你的权限模型说明、Token 结构、接口校验逻辑等,这些都是宝贵的资产。
结语:安全是系统的第一道门
回顾整个项目的开发过程,虽然遇到不少坑,但也收获满满。我深刻体会到,一个成熟的安全认证系统不仅仅是“能用”,更要“可用、易用、安全”。
现在的 Spring Security 功能越来越完善,生态也越来越丰富。掌握它不仅能帮你快速搞定基础认证,也能为你后续学习微服务、OAuth、SSO 打下坚实基础。
如果你正在学习 Spring Security 或者打算搭建类似的系统,不妨尝试从一个小项目开始实践,边学边练,你会发现,所谓“安全”其实并不神秘,它只是我们用心写出来的第一道防线。
愿你在这个旅程中少踩坑,多成长。

评论 0