从零快速搭建 Spring Security 安全认证系统:我的真实项目实战经验

霸气导师
2025-06-19 12:38
阅读 313

大家好,我是一名在一家中型互联网公司工作的后端开发工程师。我们公司做的是 ToB 的 SaaS 平台业务,用户类型包括普通员工、管理员以及合作方的接入方。作为一个核心模块,安全认证是平台的基础需求之一。

在我参与这个 SaaS 平台重构的时候,身份认证模块还是基于传统的 Session + Cookie 方案,并且和业务代码耦合得比较严重,维护起来很不方便。当时正好有机会引入更现代的认证方式,比如 JWT 和 OAuth2,同时也想尝试使用 Spring Security 来统一整个系统的权限控制流程。

这篇文章我会结合自己实际接手的项目,分享一下我是怎么一步一步用 Spring Security 快速搭建一个灵活、可扩展的安全认证系统,并在这个过程中解决遇到的各种问题。希望我的经历能给刚入门或正在学习 Spring Security 的朋友们一些启发。


开篇背景:为什么选择 Spring Security?

开篇背景:为什么选择 Spring Security?

我们的平台是典型的 Java Web 架构,前后端分离,前端为 Vue3,后端为 Spring Boot。早期的身份认证逻辑写在 Controller 层,通过自定义注解拦截请求判断是否登录,这种做法随着业务增长逐渐暴露出以下几个问题:

  • 登录状态失效没有统一出口,容易产生 bug;
  • 用户角色权限处理分散,不同接口各自校验,出错率高;
  • 接口权限难以复用,修改权限规则需要大量改代码;
  • 对接第三方系统时缺乏标准化的安全协议支持(如 OAuth2);

这些问题累积到一定程度后,项目组决定重构安全认证体系,目标是:

  1. 实现统一的认证流程;
  2. 支持 JWT;
  3. 模块化权限管理,便于未来扩展;
  4. 提升整体架构的健壮性和安全性。

Spring Security 就是在这个时候进入视野的。它虽然是出了名的复杂,但一旦理解清楚了它的过滤器链机制、AuthenticationProvider 等设计思想,你会发现它真的很强大。


遇到的挑战:Spring Security 初期上手难

遇到的挑战:Spring Security 初期上手难

缓存策略对比-2

刚开始接触 Spring Security 的时候,确实被它那一整套“术语”搞晕过一阵子。

比如:

  • FilterChainProxy 是什么?
  • SecurityProperties 和手动配置冲突怎么办?
  • 自定义登录验证流程该怎么做?
  • JWT 放哪里?如何整合进 Spring Security 的体系?

我当时在网上查了很多资料,但很多教程只是讲“跑通例子”,而没有深入讲解原理和实际部署注意事项,导致在实际项目中经常踩坑。最典型的一个问题是:登录成功后返回 JWT,但后续带 Token 请求无法通过安全层验证

这其实是对 Spring Security 的验证流程理解不深造成的。


解决方案:一步步实现安全认证系统

为了把事情说清楚,我打算按照项目实践的顺序来介绍我的实现思路,涵盖以下关键点:

1. 项目结构与依赖配置

项目使用 Spring Boot 2.7.x,Spring Security 5.6.x(版本之间兼容性很重要),加上 JWT 工具包 jjwt。

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JJWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>

然后是一个简单的登录接口:

@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginDTO loginDTO) {
    // 这里先模拟用户校验逻辑
    String token = jwtUtils.generateToken(loginDTO.getUsername());
    return ResponseEntity.ok(token);
}

此时的问题是——生成了 token,但后续请求怎么让 Spring Security 认出来?

2. 自定义 JWT 的过滤器与解析逻辑

我们需要让 Spring Security 在收到每个请求时自动检测 header 中的 token,提取用户名并构建 Authentication 对象。

于是,我创建了一个 JwtRequestFilter 类,继承 OncePerRequestFilter,重写了其中的 doFilterInternal 方法:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                FilterChain filterChain) throws ServletException, IOException {

    final String authorizationHeader = request.getHeader("Authorization");

    String username = null;
    String jwtToken = null;

    if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
        jwtToken = authorizationHeader.substring(7);
        try {
            username = jwtUtils.extractUsername(jwtToken);
        } catch (JwtException e) {
            logger.warn("Invalid JWT token: {}", e.getMessage());
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
            return;
        }
    }

    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                username, null, new ArrayList<>());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
}

这段代码的作用就是:如果请求头里有 token,就把它提取出来,构造出一个匿名的 Authentication 对象,交给 Spring Security 管理。这样后面的权限判断就能正常执行了。

3. 配置 Spring Security 的安全策略

接下来就是在 SecurityConfig 类里配置过滤器链路和放行策略:

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtRequestFilter jwtRequestFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/auth/login").permitAll()
            .anyRequest().authenticated();

        return http.build();
    }
}

这里最关键的就是 addFilterBefore(...) 把我们自己的 JWT 过滤器加进去,并让它在 Spring Security 的默认认证流程之前运行。

4. 整合角色权限校验(RBAC)

前面提到的权限散落在各接口中是个大问题,我们决定采用标准的 RBAC 模型:

  • 用户 User 可以拥有多个 Role;
  • Role 可以绑定一组 Permission;
  • 在 Controller 或 Service 层通过 @PreAuthorize("hasAuthority('PERMISSION_NAME')") 注解进行权限控制。

为了实现这一点,在 Spring Security 中我们需要开启方法级权限控制:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig { ... }

同时,我们在数据库中建立三张表:

  • users
  • roles
  • permissions
  • 关联表 user_roles, role_permissions

当用户登录后,我们在生成 JWT 的时候也一并将权限信息放入 claims:

{
  "username": "zhangsan",
  "permissions": ["view_user", "edit_order"]
}

在过滤器中解析完 token 后,就可以根据这些权限生成对应的 SimpleGrantedAuthority,最终传给 Spring Security 使用。

List<GrantedAuthority> authorities = permissions.stream()
        .map(SimpleGrantedAuthority::new)
        .collect(Collectors.toList());

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
        username, null, authorities);

这样就可以完美支持基于权限的访问控制了。


成果展示:认证系统上线后的效果

系统架构设计图-1

经过大约两周的时间,我们完成了整个认证模块的重构,取得了不错的成果:

  • 原先分散在各个服务中的权限校验逻辑全部收拢;
  • 新增权限只需添加一行 @PreAuthorize("xxx") 注解;
  • 所有接口默认都需要认证,未授权请求会直接返回 403;
  • JWT 过期时间可配,支持刷新 token 机制;
  • 日志清晰,方便调试和追踪;
  • 第三方系统接入时也可通过集成 OAuth2 快速完成授权对接。

这套认证系统后来还支撑了多租户改造、SSO 单点登录等多个新功能,成为整个平台安全体系的核心基础。


经验总结:几点建议送给后来者

虽然 Spring Security 功能强大,但它并不是万能药,也不是一开始就要用全量功能。我在使用过程中总结了一些心得,供大家参考:

1. 按需配置,别一上来就把 Security 弄得太复杂

很多人一上来就想着要配置 CAS、OAuth2、SAML……其实绝大多数业务初期只需要做好“登录 → 鉴权 → 权限控制”这一条线就够了。

2. 理解过滤器链机制,是掌握 Spring Security 的关键

不要只停留在“复制粘贴”示例代码层面,一定要去理解请求是怎么穿过一个个 filter 最终走到 controller 的。建议看一遍源码,尤其是 BasicAuthenticationFilter, UsernamePasswordAuthenticationFilter 这些默认组件。

3. 注意性能,尤其是在高并发场景下

我们在压测时发现,某些 filter 如果每次都去数据库查权限,会有明显的延迟。后来我们将用户权限信息缓存在 Redis 中,并设置了合理的 TTL,大大提升了性能。

4. 日志 + 调试工具是排查安全问题的利器

特别是在生产环境,有时候某个 API 无缘无故报 403,这时候就需要启用 debug 日志来查看是哪一层 filter 阻止了请求。

5. 安全不能只靠框架,也要结合运维手段

比如定期更新密钥、设置合适的 JWT 过期时间、限制登录失败次数等,都是必不可少的安全措施。我们配合 Nginx 设置了 IP 限流,在防止暴力破解方面起了很大作用。


写在最后:安全是一场持久战

做开发这几年,我越来越觉得,安全从来不是“一次性工程”,而是一场持续演进的过程。Spring Security 固然是一个非常棒的工具,但只有真正理解它的设计哲学,才能用得好,用得稳。

如果你也是刚开始接触安全认证这块内容,不妨从一个小项目入手,慢慢把 Spring Security 加入你的知识库中。它可能有点难,但我相信你一旦掌握了,就会爱上它那种“一切皆可插拔”的设计风格。

希望这篇来自我亲身经历的技术分享能对你有所帮助,如果你有任何疑问或建议,欢迎留言交流!

评论 0

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