从零到一:用 Spring Security 快速搭建安全认证系统

曹浩然_前端
2025-06-16 11:33
阅读 505

开篇背景:一个“不设防”的业务系统

开篇背景:一个“不设防”的业务系统

去年我加入了一个新项目,是一个典型的 ToB 系统,用户包括内部员工和外部合作伙伴。一开始团队为了快速验证产品逻辑,没有在权限控制上做太多考虑,整个系统几乎是“裸奔”状态,接口可以被任意访问,登录功能也只是个简单的 token 校验。

但随着系统上线进入测试阶段,问题接踵而至:有开发人员误删了生产数据、测试账号之间互相访问彼此的业务记录、甚至还有外部扫描工具发现了未保护的 API 接口。

作为负责后端架构的主要成员之一,我意识到必须尽快搭建一套完整的认证授权体系,而考虑到我们是基于 Spring Boot 构建的服务,Spring Security 成为首选。

这篇文章就是我在这次实战中总结下来的经验,希望能帮助你少踩坑、快上手。


遇到的问题与挑战:权限失控带来的隐患

遇到的问题与挑战:权限失控带来的隐患

1. 接口无保护,存在越权访问风险

所有接口都没有校验用户身份,任何知道 URL 的人都可以访问,并且部分业务接口还存在根据 userId 查询数据的操作(比如 /api/user/{id}),很容易出现 A 用户通过修改 id 查看 B 用户数据的情况。

2. 缺乏统一登录机制

虽然已经有前端登录页面,但后端只是在登录时返回一个简单的 token 字符串,没有任何时效性管理或续期机制,也没有将 token 和用户权限关联起来。

3. 角色权限体系缺失

系统里应该区分 admin、operator、guest 这样的角色,但在原有结构下完全没有体现出来。

4. 安全漏洞难以排查

随着第三方审计介入,发现了很多潜在的安全漏洞,比如 CSRF、XSS 漏洞检测失败、默认错误信息暴露版本号等。

这些问题如果不及时处理,一旦系统正式上线,后果不堪设想。


我的选择:为什么选 Spring Security?

我的选择:为什么选 Spring Security?

当时团队评估过几个方案:

  • Shiro:更轻量,适合对安全模块要求不太复杂的场景;
  • 自研 Token 认证中间件:可控性强,但开发周期长;
  • Spring Security:集成度高,社区活跃,文档完善,适合企业级应用;

最终我们选择了 Spring Security + JWT + OAuth2 的组合。这套体系不仅能解决基础的认证授权需求,也为未来接入 SSO、OAuth 第三方登录预留了空间。

而且 Spring Security 提供了完整的 FilterChain 流程控制,我们可以很方便地插入手动编写的鉴权逻辑。


实施过程:一步步构建起防线

我们的系统是典型的微服务架构,不过这次我们先集中在网关层和认证中心的建设上,以下是关键步骤:

1. 初步启用 Spring Security,默认拦截所有请求

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .anyRequest().authenticated();


![微服务架构示意图-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025061611/b8799a49-3ba2-4d94-a776-e28635c0eca7.jpg)


        return http.build();
    }
}

这一步其实只是为了测试环境是否已经接入成功。这时候所有接口都会自动拒绝匿名访问,并抛出 401 错误。虽然还不具备真正的登录能力,但至少把门关上了。

2. 自定义 JWT 登录流程

我们使用 JWT 来做无状态认证,所以第一步要实现登录接口:

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final AuthService authService;

    public AuthController(AuthService authService) {
        this.authService = authService;
    }

    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginDto dto) {
        String token = authService.login(dto.getUsername(), dto.getPassword());
        return ResponseEntity.ok(token);
    }
}

缓存策略对比-2

JWT 工具类生成 token 示例:

String jwt = Jwts.builder()
    .setSubject(userDetails.getUsername())
    .claim("roles", userDetails.getAuthorities().stream()
        .map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
    .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24小时有效期
    .signWith(SignatureAlgorithm.HS512, "my-secret-key")
    .compact();

3. 添加 JWT 认证过滤器链

创建一个 JWT 认证过滤器,插入到 Spring Security 的 FilterChain 中:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

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

        String token = getTokenFromRequest(request);

        if (token != null && jwtUtil.validateToken(token)) {
            UsernamePasswordAuthenticationToken authentication = jwtUtil.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        chain.doFilter(request, response);
    }

    // ...
}

// 插入到 UsernamePasswordAuthenticationFilter 之前
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
    // ...其他配置
}

4. RBAC 动态权限控制设计

我们参考的是经典的 RBAC 模型:

  • 用户表(User)
  • 角色表(Role)
  • 权限表(Permission)
  • 用户-角色映射表(UserRole)
  • 角色-权限映射表(RolePermission)

然后,在接口层面配合 Spring Security 的方法级注解来控制访问权限:

@GetMapping("/admin-panel")
@PreAuthorize("hasAuthority('PERMISSION_ADMIN_PANEL')")
public String getAdminPanel() {
    return "Welcome to Admin Panel";
}

为了让这个机制能动态支持不同的权限,我们在启动时就把数据库中的权限加载进内存并缓存起来:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/api/**").access("@dynamicSecurityService.checkPermission(authentication,request)")
        .anyRequest().authenticated();
}

这段代码里的 @dynamicSecurityService.checkPermission 是通过 Spring EL 表达式调用我们自己写的权限判断服务,实现灵活控制。

5. 跨域与安全头补缺

前后端分离之后,跨域问题必须处理,我们添加了全局配置:

@Configuration
@RequiredArgsConstructor
public class CorsConfig implements WebMvcConfigurer {

    private final Environment env;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins(env.getProperty("frontend.url"))
                .allowCredentials(true)
                .allowedMethods("*");
    }
}

另外,在响应头中加入了基本的 CSP、X-Content-Type-Options 等内容防止 XSS:

http.headers()
    .contentSecurityPolicy("default-src 'self'; script-src 'self' https://trusted-cdn.com")
    .and()
    .xssProtection().block(true);

效果与收益:系统终于有了安全感

这套机制上线后,我们收获了很多好处:

✅ 安全性大大提升

  • 所有接口都经过身份校验
  • 不同用户只能访问自己的数据,RBAC 控制权限细粒度化
  • 日志中可以追踪每个请求是由哪个用户发起的

✅ 接口文档更规范、更清晰

由于引入了 Spring Security 注解风格的权限标记,Swagger 可以自动识别哪些接口需要什么权限,也方便测试团队理解。

✅ 性能影响几乎不可察觉

因为 JWT 是无状态认证机制,不需要频繁查询数据库。我们在压测环境下模拟了并发登录 + 多接口调用,整体性能损耗不到 5ms。

✅ 后续扩展成本降低

当我们后来接入 OIDC、集成钉钉扫码登录时,Spring Security 已有的结构让我们可以轻松新增各种 Provider,省去了很多重复开发工作。


经验分享:踩过的坑,希望你不用再踩

⚠️ 小心 Session 管理方式的影响

Spring Security 默认使用 session-based 认证,对于前后端分离的应用建议关闭,改为 stateless:

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

不然你会发现每次重启服务,用户都需要重新登录,或者 session 数据丢失。

⚠️ 方法级别的权限注解不是银弹

@PreAuthorize@Secured 这种注解非常实用,但也有局限性:

  • 需要在配置中显式开启 @EnableGlobalMethodSecurity
  • 修改权限后不会即时生效,除非重启或清空缓存
  • 对于大量接口时维护成本很高,推荐搭配动态路由策略或接口权限中心一起使用

⚠️ 数据库权限模型要尽早设计好

我们是在系统中期才开始梳理权限结构的,结果发现很多接口缺少明确权限边界,临时加权限的时候容易漏掉。

建议大家从初期就规划好各个模块的权限点,并在接口开发时同步打上注释或注解,减少后期重构成本。

⚠️ 生产环境务必开启详细的日志追踪

我们早期在日志中记录了用户 ID + 请求路径 + HTTP Method,后续结合日志分析平台做了很多行为追踪和异常监控,非常有用。

比如,我们可以快速定位是谁删除了哪条数据,或者某个敏感接口被谁高频调用,这些都能帮助运营和安全部门做出决策。


结语:不只是工具,更是一套方法论

回过头来看,Spring Security 其实不仅仅是一个框架,它背后蕴含着一整套认证、授权、会话管理的方法论。我在实际项目中也不断调整思路,比如最开始以为只要加个 JWT 解析就可以,后来才发现还需要 RBAC 支持、CSRF 防御、动态权限加载等。

如今我们这套认证系统已经在多个项目中复用,成为公司统一的用户中心一部分。而我对 Spring Security 的理解和信心,也是在一次次上线、报错、修复过程中建立起来的。

如果你也在构建一个需要权限控制的系统,不妨尝试一下这套方案。也许刚开始会觉得复杂,但坚持用下来,你会发现它远比你想象的强大得多。


📌 最后送你一句心得

“安全是个持续的过程,永远没有‘完成’这一说。”
不要追求完美,而是要让系统具备可演化、易维护的能力。

评论 0

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