Appearance
Spring Security
Spring Security 是 Spring 生态系统中最强大、最广泛使用的安全框架,为 Java 应用提供全面的认证(Authentication)和授权(Authorization)支持。
Spring Security 6 推荐使用 自定义 Controller + AuthenticationManager 模式,而非传统的 UsernamePasswordAuthenticationFilter
RESTful
| 端点 | 方法 | 认证要求 | 说明 |
|---|---|---|---|
/auth/login | POST | 公开 | 用户名密码登录,返回 Token 对 |
/auth/refresh | POST | 需有效 RefreshToken | 换取新的 AccessToken(令牌轮换) |
/auth/logout | POST | 需认证 | 撤销当前 AccessToken(黑名单机制) |
/auth/logout-all | POST | 需认证 | 撤销用户所有令牌(强制重新登录) |
过滤链
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.FilterChain和SpringSecurityFilterChain导致单次请求两次执行。 - 通过
@Configuration配置类定义SecurityFilter
Filter
java
public interface Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
}| 顺序 | 过滤器类名 | 核心职责 |
|---|---|---|
| 1 | ChannelProcessingFilter | 强制 HTTPS/HTTP 通道安全 |
| 2 | WebAsyncManagerIntegrationFilter | 集成 Spring WebAsyncManager |
| 3 | SecurityContextPersistenceFilter | 请求间持久化 SecurityContext(5.7+ 更名为 SecurityContextHolderFilter) |
| 4 | HeaderWriterFilter | 添加安全响应头(X-Frame-Options, CSP 等) |
| 5 | CorsFilter | 处理跨域请求 |
| 6 | CsrfFilter | 防御 CSRF 攻击 |
| 7 | LogoutFilter | 处理注销请求 /logout |
| 8 | OAuth2AuthorizationRequestRedirectFilter | OAuth2 授权端点重定向 |
| 9 | Saml2WebSsoAuthenticationRequestFilter | SAML2 认证请求处理 |
| 10 | X509AuthenticationFilter | X.509 证书认证 |
| 11 | AbstractPreAuthenticatedProcessingFilter | 预认证场景(如反向代理) |
| 12 | CasAuthenticationFilter | CAS 单点登录 |
| 13 | OAuth2LoginAuthenticationFilter | OAuth2 登录回调处理 |
| 14 | Saml2WebSsoAuthenticationFilter | SAML2 SSO 回调 |
| 15 | UsernamePasswordAuthenticationFilter | 表单登录认证 /login |
| 16 | OpenIDAuthenticationFilter | OpenID 认证(已废弃) |
| 17 | DefaultLoginPageGeneratingFilter | 生成默认登录页 |
| 18 | DefaultLogoutPageGeneratingFilter | 生成默认注销页 |
| 19 | ConcurrentSessionFilter | 并发会话控制 |
| 20 | DigestAuthenticationFilter | HTTP Digest 认证 |
| 21 | BearerTokenAuthenticationFilter | JWT/OAuth2 Bearer Token 认证 |
| 22 | BasicAuthenticationFilter | HTTP Basic 认证 |
| 23 | RequestCacheAwareFilter | 认证后恢复原始请求 |
| 24 | SecurityContextHolderAwareRequestFilter | 包装请求支持 Servlet API 安全方法 |
| 25 | JaasApiIntegrationFilter | 集成 JAAS |
| 26 | RememberMeAuthenticationFilter | Remember-Me 自动登录 |
| 27 | AnonymousAuthenticationFilter | 为匿名用户创建认证对象 |
| 28 | OAuth2AuthorizationCodeGrantFilter | OAuth2 授权码模式 |
| 29 | SessionManagementFilter | 会话固定保护、并发控制 |
| 30 | ExceptionTranslationFilter | 处理 AuthenticationException 和 AccessDeniedException |
| 31 | FilterSecurityInterceptor | 最终授权决策(访问控制核心) |
| 32 | SwitchUserFilter | 管理员切换用户身份 |
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter(5.7+ 更名为SecurityContextHolderFilter) 是 Spring Security 过滤器链中的第一个过滤器。它负责从请求中恢复 SecurityContext,并在请求处理完成后清理 SecurityContext。SecurityContext 持有当前认证用户的信息。
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 标记为已使用/吊销
│
◄────────────────────────────────────────────┘