Skip to content

Spring Security

Spring Security 是 Spring 生态系统中最强大、最广泛使用的安全框架,为 Java 应用提供全面的认证(Authentication)和授权(Authorization)支持。

Spring Security 6 推荐使用 自定义 Controller + AuthenticationManager 模式,而非传统的 UsernamePasswordAuthenticationFilter

RESTful

端点方法认证要求说明
/auth/loginPOST公开用户名密码登录,返回 Token 对
/auth/refreshPOST需有效 RefreshToken换取新的 AccessToken(令牌轮换)
/auth/logoutPOST需认证撤销当前 AccessToken(黑名单机制)
/auth/logout-allPOST需认证撤销用户所有令牌(强制重新登录)

过滤链

  • org.springframework.security.web.SecurityFilterChain
  • 多级链式结构:采用“链中链”设计。
  • 过滤器数量动态,根据需求配置。

org.springframework.web.filter.DelegatingFilterProxy

plain
Servlet FilterChain
    └── DelegatingFilterProxy (Spring 集成入口)
            └── FilterChainProxy (Spring Security 核心调度器)
                    ├── SecurityFilterChain 1 (匹配 /api/**) → 包含 15 个过滤器
                    ├── SecurityFilterChain 2 (匹配 /admin/**) → 包含 20 个过滤器  
                    └── SecurityFilterChain 3 (匹配 /**) → 包含 12 个过滤器
  • org.springframework.security.web.FilterChainProxy
java

public class FilterChainProxy extends GenericFilterBean {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (!clearContext) {
            this.doFilterInternal(request, response, chain);
        } else {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } catch (Exception var11) {
                Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var11);
                Throwable requestRejectedException = this.throwableAnalyzer.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
                if (!(requestRejectedException instanceof RequestRejectedException)) {
                    throw var11;
                }

                this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, (RequestRejectedException)requestRejectedException);
            } finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }

        }
    }

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
        List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
        if (filters != null && filters.size() != 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(LogMessage.of(() -> {
                    return "Securing " + requestLine(firewallRequest);
                }));
            }

            VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
            virtualFilterChain.doFilter(firewallRequest, firewallResponse);
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace(LogMessage.of(() -> {
                    return "No security for " + requestLine(firewallRequest);
                }));
            }

            firewallRequest.reset();
            chain.doFilter(firewallRequest, firewallResponse);
        }
    }
}

过滤器

  • OncePreRequestFilter extends GenericFilterBean implements javax.servlet.Filter
  • 自定义过滤器不要使用@Component@Bean注入,那样将会注册到javax.servlet.FilterChainSpringSecurityFilterChain导致单次请求两次执行。
  • 通过@Configuration配置类定义SecurityFilter

Filter

java
public interface Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;
}
顺序过滤器类名核心职责
1ChannelProcessingFilter强制 HTTPS/HTTP 通道安全
2WebAsyncManagerIntegrationFilter集成 Spring WebAsyncManager
3SecurityContextPersistenceFilter请求间持久化 SecurityContext(5.7+ 更名为 SecurityContextHolderFilter
4HeaderWriterFilter添加安全响应头(X-Frame-Options, CSP 等)
5CorsFilter处理跨域请求
6CsrfFilter防御 CSRF 攻击
7LogoutFilter处理注销请求 /logout
8OAuth2AuthorizationRequestRedirectFilterOAuth2 授权端点重定向
9Saml2WebSsoAuthenticationRequestFilterSAML2 认证请求处理
10X509AuthenticationFilterX.509 证书认证
11AbstractPreAuthenticatedProcessingFilter预认证场景(如反向代理)
12CasAuthenticationFilterCAS 单点登录
13OAuth2LoginAuthenticationFilterOAuth2 登录回调处理
14Saml2WebSsoAuthenticationFilterSAML2 SSO 回调
15UsernamePasswordAuthenticationFilter表单登录认证 /login
16OpenIDAuthenticationFilterOpenID 认证(已废弃)
17DefaultLoginPageGeneratingFilter生成默认登录页
18DefaultLogoutPageGeneratingFilter生成默认注销页
19ConcurrentSessionFilter并发会话控制
20DigestAuthenticationFilterHTTP Digest 认证
21BearerTokenAuthenticationFilterJWT/OAuth2 Bearer Token 认证
22BasicAuthenticationFilterHTTP Basic 认证
23RequestCacheAwareFilter认证后恢复原始请求
24SecurityContextHolderAwareRequestFilter包装请求支持 Servlet API 安全方法
25JaasApiIntegrationFilter集成 JAAS
26RememberMeAuthenticationFilterRemember-Me 自动登录
27AnonymousAuthenticationFilter为匿名用户创建认证对象
28OAuth2AuthorizationCodeGrantFilterOAuth2 授权码模式
29SessionManagementFilter会话固定保护、并发控制
30ExceptionTranslationFilter处理 AuthenticationExceptionAccessDeniedException
31FilterSecurityInterceptor最终授权决策(访问控制核心)
32SwitchUserFilter管理员切换用户身份

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter(5.7+ 更名为SecurityContextHolderFilter) 是 Spring Security 过滤器链中的第一个过滤器。它负责从请求中恢复 SecurityContext,并在请求处理完成后清理 SecurityContextSecurityContext 持有当前认证用户的信息。

java
public class SecurityContextHolderFilter extends OncePerRequestFilter {
	public class SecurityContextHolderFilter extends OncePerRequestFilter {
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        SecurityContext securityContext = (SecurityContext)this.securityContextRepository.loadContext(request).get();

        try {
            SecurityContextHolder.setContext(securityContext);
            filterChain.doFilter(request, response);
        } finally {
            SecurityContextHolder.clearContext();
        }

    }
}
}

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter 负责处理基于用户名和密码的表单登录认证。它通常拦截 /login 请求,验证用户凭证,并创建一个 Authentication 对象

java
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        this.doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
    }

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            try {
                Authentication authenticationResult = this.attemptAuthentication(request, response);
                if (authenticationResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authenticationResult, request, response);
                if (this.continueChainBeforeSuccessfulAuthentication) {
                    chain.doFilter(request, response);
                }

                this.successfulAuthentication(request, response, chain, authenticationResult);
            } catch (InternalAuthenticationServiceException var5) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
                this.unsuccessfulAuthentication(request, response, var5);
            } catch (AuthenticationException var6) {
                this.unsuccessfulAuthentication(request, response, var6);
            }

        }
    }
}

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username.trim() : "";
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
}

BasicAuthenticationFilter

BasicAuthenticationFilter 负责处理 HTTP Basic 认证。它从请求头中提取用户名和密码,并进行认证。

java
public class BasicAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Basic ")) {
            chain.doFilter(request, response);
            return;
        }
        try {
            String[] tokens = extractAndDecodeHeader(header, request);
            assert tokens.length == 2;
            String username = tokens[0];
            // 根据用户名和密码进行认证
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, tokens[1]);
            Authentication authResult = this.getAuthenticationManager().authenticate(authRequest);
            // 将认证结果设置到 SecurityContext
            SecurityContextHolder.getContext().setAuthentication(authResult);
        } catch (AuthenticationException failed) {
            // 处理认证失败
            SecurityContextHolder.clearContext();
            this.authenticationEntryPoint.commence(request, response, failed);
            return;
        }
        chain.doFilter(request, response);
    }
}

CsrfFilter

CsrfFilter 负责处理跨站点请求伪造(CSRF)攻击。它生成和验证 CSRF 令牌,确保请求的合法性。

java
public class CsrfFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if (csrfToken != null) {
            // 验证 CSRF 令牌
            String actualToken = request.getHeader(csrfToken.getHeaderName());
            if (actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
            }
            if (!csrfToken.getToken().equals(actualToken)) {
                throw new InvalidCsrfTokenException(csrfToken, actualToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

ExceptionTranslationFilter

ExceptionTranslationFilter 负责捕获过滤器链中的任何异常,并将其转换为适当的 HTTP 响应。例如,当用户未认证时,可以将异常转换为 401 Unauthorized 响应。

java
public class ExceptionTranslationFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            chain.doFilter(request, response);
        } catch (AuthenticationException ex) {
            // 处理认证异常
            commenceAuthentication((HttpServletRequest) request, (HttpServletResponse) response, ex);
        } catch (AccessDeniedException ex) {
            // 处理访问拒绝异常
            handleAccessDenied((HttpServletRequest) request, (HttpServletResponse) response, ex);
        }
    }
    private void commenceAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException, ServletException {
        // 将异常转换为 401 Unauthorized 响应
        SecurityContextHolder.clearContext();
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
    }
    private void handleAccessDenied(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException {
        // 将异常转换为 403 Forbidden 响应
        response.sendError(HttpServletResponse.SC_FORBIDDEN, ex.getMessage());
    }
}

FilterSecurityInterceptorFilterSecurityInterceptor 是过滤器链中的最后一个过滤器,负责执行最终的访问控制决策。它检查用户是否有权限访问请求的资源。

java
- `public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {`

-     `@Override`
-     `public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {`
-         `FilterInvocation fi = new FilterInvocation(request, response, chain);`
-         `invoke(fi);`
-     `}`

-     `public void invoke(FilterInvocation fi) throws IOException, ServletException {`
-         `if (fi.getRequest() != null && fi.getRequest().getMethod().equals("OPTIONS"))`

-  `{`
-             `fi.getChain().doFilter(fi.getRequest(), fi.getResponse());`
-             `return;`
-         `}`

-         `// 执行访问控制决策`
-         `InterceptorStatusToken token = super.beforeInvocation(fi);`

-         `try {`
-             `// 执行过滤器链中的下一个过滤器`
-             `fi.getChain().doFilter(fi.getRequest(), fi.getResponse());`
-         `} finally {`
-             `super.afterInvocation(token, null);`
-         `}`
-     `}`
- `}`

自定义过滤器

例如:创建一个记录请求日志的自定义过滤器

  • 创建:自定义过滤器需要实现 javax.servlet.Filter 接口或扩展 Spring Security 提供的过滤器基类。
java
public class RequestLoggingFilter extends GenericFilterBean {
    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        logger.info("Incoming request: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());
        chain.doFilter(request, response);
    }
}
  • 注册:注册到 Spring Security 的过滤器链中。可以通过扩展 WebSecurityConfigurerAdapter 并在配置中添加自定义过滤器。
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private RequestLoggingFilter requestLoggingFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(requestLoggingFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll()
            .and()
            .logout()
            .permitAll();
    }
}

令牌轮换(access token、refresh_token )

安全性要求 Token 短有效期,用户体验要求避免频繁登录。双 Token 设计完美平衡这两点

特性Access Token (JWT)Refresh Token
格式JWT(包含 Header.Payload.Signature)高强度随机字符串(非 JWT)
自包含信息用户ID、权限、过期时间等仅数据库映射 ID(无敏感信息)
验证方式本地验签(无需查询数据库)必须查询授权服务器数据库
撤销难度困难(需维护黑名单)容易(数据库标记即可)
  • Access Token:追求性能,无状态,快速验证
  • Refresh Token:追求可控,有状态,支持即时吊销

每次使用 Refresh Token 换取新 Access Token 时,必须同时颁发新的 Refresh Token,旧 Refresh Token 立即作废

plain
┌─────────────┐     1. 登录认证      ┌─────────────┐
│    客户端     │ ──────────────────▶ │  授权服务器   │
│  (浏览器/App) │                    │              │
└─────────────┘                    └──────┬──────┘

                    2. 颁发 Token 对(同时下发)
                    ├─ Access Token (JWT, 15分钟)
                    └─ Refresh Token (随机字符串, 7天)

◄─────────────────────────────────────────┘

┌─────────────┐     3. 访问 API        ┌─────────────┐
│    客户端     │ ────────────────────▶ │   资源服务器  │
│   Bearer     │  Authorization:       │  验证 JWT    │
│   Token      │  Bearer <access_token>│  签名/有效期  │
└─────────────┘                       └─────────────┘

                        4. 验证通过,返回资源数据 ◄───┘

[15分钟后 Access Token 过期]

┌─────────────┐     5. 刷新请求             ┌─────────────┐
│    客户端     │ ────────────────────▶     │  授权服务器   │
│  Refresh     │  POST /oauth/token       │  验证:      │
│   Token      │  grant_type=refresh_token│  - Refresh Token 有效性
│              │  &refresh_token=xxx      │  - 客户端身份(client_secret)
└─────────────┘                           │  - 是否被吊销  │
                                          └──────┬──────┘

									6. 颁发新 Token 对(令牌轮换)
									├─ 新的 Access Token
									├─ **新的 Refresh Token**(旧作废)[^20^]
									└─ 旧的 Refresh Token 标记为已使用/吊销

◄────────────────────────────────────────────┘

REF

https://yanfukun.com/read/security-guide/intro