用 Spring Security 快速搭建一个安全认证系统:我的实战经验分享

算法曹建国导师
2025-06-17 17:52
阅读 678

引言:为什么会选择写这篇?

引言:为什么会选择写这篇?

我是一名后端开发工程师,在工作中经常需要为不同的业务系统搭建基础的安全认证模块。有一次我们团队接手了一个新的项目,客户希望在两周内交付一个后台管理系统的基本框架,包括登录、权限控制、角色管理等核心功能。

为了快速实现这些需求,我们决定使用 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(不涉及本文重点)

整个认证流程的大致思路如下:

  1. 用户通过 /login 接口提交用户名密码;
  2. 系统验证后生成包含用户信息和角色的 JWT;
  3. 后续请求需携带 Token,由拦截器解析并设置当前用户上下文;
  4. 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。

这里我们做了几个关键点:

  1. 自定义了一个 JwtAuthenticationFilter,继承 OncePerRequestFilter,确保每个请求只执行一次。
  2. 使用 Redis 缓存 Token 的吊销状态,避免无效 Token 继续生效。
  3. 在登录成功后返回 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;


![服务器部署方案-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025061717/f909acdb-62fc-42e4-9ae3-ec9068be63d1.jpg)


    // ...
}

在用户登录时,根据用户名从数据库查出该用户对应的所有角色权限,然后构造 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝