Skip to content

Spring Security 从零到生产级学习总览

Spring Security 不是一个“JWT 工具类”,也不是几条 requestMatchers 配置。它是一套围绕认证、授权、安全上下文、Web 攻击防护、会话管理和安全扩展建立的框架。对 Servlet 应用来说,请求通常在到达 Controller 之前就由安全过滤器链决定身份是否可信、访问是否允许以及失败如何响应。

本专栏以 JDK 8 + Spring Boot 2.7 + Spring Security 5.7 为存量基线,再说明 Boot 3/Security 6/Java 17 的差异。安全基础中的哈希、HMAC、数字签名、证书与 TLS 请先结合信息安全专栏学习。

一、真正学懂的验收标准

你需要能回答:

  1. 请求怎样从 Servlet 容器进入 Spring Security,又怎样回到业务 Filter 链?
  2. DelegatingFilterProxyFilterChainProxySecurityFilterChain 分别是什么?
  3. 多条 SecurityFilterChain 怎样选择,是否会把所有链串起来执行?
  4. Authentication 在认证前后分别保存什么?
  5. ProviderManager 怎样选择 AuthenticationProvider
  6. 用户名密码认证如何加载用户、校验密码并擦除凭证?
  7. SecurityContext 怎样创建、保存到 Session、恢复并在请求结束时清理?
  8. 401 与 403 分别在哪里产生,匿名用户为什么可能最终得到 401?
  9. JWT 为什么是签名载荷而不是加密载荷,泄露后如何撤销?
  10. CSRF 与 CORS 分别解决什么问题,为什么不能看到 Token 就一律关闭 CSRF?
  11. URL 授权、方法授权和数据权限如何分层?
  12. 线上出现错误 401/403、登录循环、上下文串号时如何获取证据?

二、正确学习路线

mermaid
flowchart TD
    A["HTTP、Cookie、Session 与密码哈希"] --> B["Servlet Filter 基础"]
    B --> C["Security 过滤器链与上下文"]
    C --> D["用户名密码认证与 Provider"]
    D --> E["Session 登录与状态恢复"]
    E --> F["JWT、刷新与撤销"]
    F --> G["URL、方法与数据授权"]
    G --> H["CSRF、CORS 与常见攻击"]
    H --> I["商业权限模型与审计"]
    I --> J["测试、监控和生产排查"]

推荐顺序:

  1. 过滤器链全过程:所有 Web 安全能力的入口。
  2. 核心架构:Authentication、Manager、Provider 和 Context。
  3. 登录认证:用户名密码、Session 与认证事件。
  4. JWT 认证:签发、验证、刷新、撤销和密钥轮换。
  5. 权限控制:URL、方法和数据权限。
  6. CSRF 与 CORS:浏览器安全模型。
  7. 商业场景训练营:组合到医疗、后台与接口系统。
  8. 验收清单:逐项证明掌握程度。
  9. 面试题:标准回答跳回原理锚点。

三、认证与授权不能混淆

概念回答的问题例子失败结果
认证 Authentication你是谁,凭证是否可信密码、Session、JWT、证书未认证,通常进入认证失败入口
授权 Authorization已知身份是否允许操作资源order:refund、管理员角色已认证但无权限,通常 403
数据权限对同类资源能看哪一行数据只能看本医院、本人订单需要业务查询条件和领域校验
审计 Audit谁在何时做了什么导出患者数据、退款审批记录证据,不替代授权

认证成功不代表拥有所有权限;有一个角色名称也不代表自动具备业务数据范围。

四、请求为什么在 Controller 前被拦截

mermaid
flowchart TD
    A["Tomcat 接收 HTTP 请求"] --> B["Servlet 容器 Filter 链"]
    B --> C["DelegatingFilterProxy"]
    C --> D["Spring Bean:FilterChainProxy"]
    D --> E["选择第一条匹配的 SecurityFilterChain"]
    E --> F["安全过滤器依次执行"]
    F --> G["恢复身份、认证、异常翻译和授权"]
    G --> H{"是否允许访问"}
    H -- "否" --> I["写入 401 或 403 响应"]
    H -- "是" --> J["继续进入 DispatcherServlet"]
    J --> K["Controller 和 Service"]

DelegatingFilterProxy 是 Servlet Filter,它把调用委托给 Spring 容器中的安全 Filter Bean;FilterChainProxy 管理多条 SecurityFilterChain;每条链包含一组有顺序的安全过滤器。

不是每个请求都执行所有 SecurityFilterChain。通常按配置顺序选择第一条 RequestMatcher 匹配的链,然后执行该链内部过滤器。具体过程见过滤器链全过程

五、核心对象地图

对象核心职责常见误解
Authentication保存 principal、credentials、authorities 和认证状态不只代表已登录对象,也可表示待认证请求
SecurityContext当前执行上下文中的 Authentication 容器不是永久用户数据库
SecurityContextHolder提供上下文存取策略,Servlet 常见基于 ThreadLocal请求结束不清理会有线程复用风险
AuthenticationManager认证入口接口不一定亲自查用户和校验密码
ProviderManager遍历支持当前 Token 类型的 Provider不是所有 Provider 都无条件执行
AuthenticationProvider执行一种认证机制用户密码、短信、API Key 可各自实现
UserDetailsService按用户名加载用户安全信息不负责比较提交密码
PasswordEncoder哈希密码并验证BCrypt 不是可逆加密
GrantedAuthority表示角色或细粒度权限字符串ROLE_ADMINadmin 语义受表达式影响
SecurityContextRepository在请求之间加载和保存上下文JWT 无状态模式也要明确保存策略

六、用户名密码认证主链

mermaid
flowchart TD
    A["过滤器读取用户名和密码"] --> B["创建未认证 Authentication"]
    B --> C["AuthenticationManager.authenticate"]
    C --> D["ProviderManager 查找 supports=true 的 Provider"]
    D --> E["DaoAuthenticationProvider"]
    E --> F["UserDetailsService 查询用户"]
    F --> G["检查锁定、禁用、过期状态"]
    G --> H["PasswordEncoder.matches 校验密码"]
    H --> I{"认证是否成功"}
    I -- "否" --> J["抛出 AuthenticationException"]
    I -- "是" --> K["返回已认证 Authentication"]
    K --> L["写入 SecurityContext"]
    L --> M["按策略保存到 Session 或仅当前请求"]

密码比较不是“再次 encode 输入密码然后比较字符串”。BCrypt 每次编码会生成随机盐,正确方式是 matches(rawPassword, storedHash),编码器从已有哈希中读取算法参数和盐进行验证。

七、Session 与 JWT 不是新旧替代关系

对比SessionJWT Access Token
状态主要保存服务端 Session 存储Token 自身携带声明,服务端可保持较少状态
浏览器常见传输Cookie 自动携带 SessionIdAuthorization Header 或 Cookie
立即撤销删除或失效 Session 较直接需短有效期、黑名单、版本号等机制
多实例共享 Session、粘性会话或集中存储各实例能使用同一验签密钥或公钥
权限变更下次加载或更新 Session长 Token 中权限快照可能滞后
CSRF 风险Cookie 自动携带时重点防护放 Cookie 仍有风险;Header 通常不被第三方页面自动附带

JWT payload 一般可被客户端解码看到;签名主要保证完整性和签发者真实性,不自动提供机密性。敏感数据不应放入 payload。

八、SecurityContext 的请求生命周期

mermaid
flowchart TD
    A["请求进入安全链"] --> B["创建空 SecurityContext"]
    B --> C["从 Session 或 Repository 加载旧上下文"]
    C --> D["认证过滤器可能更新 Authentication"]
    D --> E["授权和业务代码读取当前身份"]
    E --> F["按策略保存变更后的上下文"]
    F --> G["finally 清理 SecurityContextHolder"]
    G --> H["Tomcat 线程回到线程池"]

最后的清理不能省。Servlet 工作线程会复用;ThreadLocal 身份残留可能让后续请求读取到错误用户。异步线程也不会天然安全继承上下文,需要显式传播策略,并防止在线程池中泄漏。

九、401 和 403 为什么容易看错

情况Security 视角常见处理
没有凭证访问受保护资源匿名、未完成认证AuthenticationEntryPoint,通常 401
Token 无效或过期认证失败认证失败处理或 EntryPoint,通常 401
已认证但权限不足授权拒绝AccessDeniedHandler,通常 403
CSRF Token 缺失访问拒绝异常常见 403,但不是角色不足
CORS 预检失败可能在浏览器或 CORS Filter 阶段失败不应简单归类为登录失败

ExceptionTranslationFilter 不是捕获所有 Java 异常的全局处理器。它重点翻译安全链下游的 AuthenticationExceptionAccessDeniedException。业务异常仍应由 MVC 异常体系处理。

十、密码为什么要用自适应哈希

密码存储目标不是未来解密出原文,而是验证提交值是否一致。推荐使用 BCrypt、Argon2、PBKDF2、SCrypt 等带盐且可调成本的密码哈希。

java
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(10);
}

注册时:

java
String encoded = passwordEncoder.encode(rawPassword);

登录时:

java
boolean matched = passwordEncoder.matches(rawPassword, encoded);

不要使用明文、普通 MD5 或无盐 SHA-256 存密码。攻击者拿到数据库后可以高速离线尝试;自适应哈希通过随机盐抵抗彩虹表,并通过成本参数提高每次猜测代价。

十一、Boot 2 与 Boot 3 配置边界

基线主要方式注意事项
Boot 2.6 及更早常见WebSecurityConfigurerAdapter已弃用,不建议新写
Boot 2.7/Security 5.7/JDK 8SecurityFilterChain Bean本专栏存量基线
Boot 3/Security 6/Java 17组件式配置、requestMatchersjavax.servlet 迁移到 jakarta.servlet

Boot 2.7/JDK 8 示例可使用组件式配置,但 DSL 细节与 Security 6 不完全相同。复制代码前先确认依赖版本;不要为了让新 DSL 编译,直接把存量系统一半依赖升级到 Boot 3。

十二、JDK 8 + Boot 2.7 最小配置

java
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http)
            throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .httpBasic()
            .and()
            .csrf().disable();
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("change-me"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(admin);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

这只是可运行的过滤器链验证配置,不是生产账号系统。生产用户应来自数据库或身份平台,密码不能写在源码,CSRF 是否关闭要根据凭证传输方式判断。

十三、商业系统安全边界

医疗数据平台至少要区分:

  • 平台管理员、医院管理员、采集运维、审计人员和只读用户。
  • 功能权限,例如启动采集、重跑批次、导出数据。
  • 数据权限,例如只能访问所属医院和授权科室。
  • 高风险操作二次确认、审批或双人复核。
  • 登录失败、权限拒绝、导出和配置变更审计。

URL 白名单只解决入口是否公开,不能替代 Service 层的数据归属检查。一个拥有 patient:read 的用户也不应读取其他医院患者数据。

十四、生产排查入口

请求一直 401

检查凭证是否到达应用、请求匹配哪条 SecurityFilterChain、认证过滤器是否执行、Token/Session 是否有效、Provider 是否支持当前 Authentication 类型、上下文是否在授权前建立。

登录成功后下一请求仍未登录

Session 模式检查 Set-Cookie、Cookie Domain/Path/SameSite、代理 HTTPS、Session 存储和上下文保存;JWT 模式检查前端是否携带 Authorization、网关是否转发 Header、过滤器是否验证并写入上下文。

请求 403

先区分权限不足、CSRF、CORS 预检还是请求匹配了错误链;再检查 authority 字符串、ROLE_ 前缀、方法安全是否启用和数据权限。

Controller 根本没有日志

很可能在 Filter/Security/MVC 映射前被拦截。打开有范围的 Security DEBUG 日志,观察链选择和匿名/认证状态;生产环境不要长期全量 TRACE。

十五、面试标准回答

问:Spring Security 核心原理是什么?

Servlet 请求先经过 DelegatingFilterProxy,它把调用委托给 Spring 容器中的 FilterChainProxy。FilterChainProxy 按顺序选择第一条匹配的 SecurityFilterChain,再执行其中的上下文、认证、异常翻译和授权过滤器。认证过滤器把凭证封装成未认证 Authentication,交给 AuthenticationManager;常见 ProviderManager 选择支持该 Token 类型的 AuthenticationProvider,认证成功后返回带 principal 和 authorities 的已认证对象并写入 SecurityContext。授权阶段根据请求规则或方法规则判断权限;未认证由 AuthenticationEntryPoint 处理,已认证但权限不足由 AccessDeniedHandler 处理。请求结束后必须保存需要持久化的上下文并清理 ThreadLocal。

标准回答之后应继续回到过滤器链全过程核心架构理解每一步。

十六、问题到知识点跳转

问题页面
请求到底经过哪些安全对象过滤器链全过程
Manager、Provider、Context 的关系核心架构
用户名密码怎样认证登录认证
JWT 怎样签发、验证和撤销JWT 认证
URL、方法和数据权限权限控制
CSRF 与 CORS 为什么不同CSRF 与 CORS
怎样在商业项目落地商业场景训练营
怎样准备面试面试题

本章小结

Spring Security 的主线是:Servlet Filter 把请求交给 Spring 管理的安全代理,匹配一条安全链,加载或创建 SecurityContext,认证机制验证凭证并建立身份,授权机制判断是否允许访问,异常组件把安全失败转换成 401/403,最后保存需要持久化的身份并清理线程上下文。

只有能解释每一步的处理对象、状态变化、失败结果和排查证据,才算真正理解 Spring Security。