- SecurityFilterChain
- FilterChainProxy
- 内置SecurityFilter
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- UsernamePasswordAuthenticationFilter
- ConcurrentSessionFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
讲过滤器[Security Filter]之前,首先要提到SecurityFilterChain。
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List getFilters();
}
SecurityFilterChain包含了两个方法。
- matches(HttpServletRequest request):主要是提供给FilterChainProxy进行请求的路由。
- List getFilters(): 提供SecurityFilterChain中的Security Filter。
FilterChainProxy继承于GenericFilterBean,是spring容器中的一个过滤器。
它主要做了两个工作:
- 内置了一个防火强HttpFirewall。对ServletRequest中的HttpMethod、ContextPath、Hostname、uri等做了限制。具体内容可以查看StrictHttpFirewall::getFirewalledRequest。
- 执行内部的SecurityFilterChain,直到SecurityFilterChain中的Security Filter执行完毕后,继续执行Servlet Container中的剩余的Filter。详见FilterChainProxy.VirtualFilterChain::doFilter:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//如果SecurityFilterChain中的最后一个SecurityFilter执行完成,则继续执行原Servlet容器中的过滤器
if (this.currentPosition == this.size) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
return;
}
//SecurityFilterChain标识位移动到下个
this.currentPosition++;
//获取SecurityFilterChain中的下一个Security Filter
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
this.currentPosition, this.size));
}
//执行获取的这个Security Filter
nextFilter.doFilter(request, response, this);
}
内置SecurityFilter
ChannelProcessingFilter
ChannelProcessingFilter主要负责检测当前请求的安全通道secure channel是否符合配置要求。
主要有三种配置:
- REQUIRES_INSECURE_CHANNEL: 当前请求没有使用了安全通道,例如https请求。
- REQUIRES_SECURE_CHANNEL: 当前请求使用了安全通道,例如http请求。
- ANY_CHANNEL: 没有限制。
配置用法举例:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//访问/admin-api,需要以https开头的方式访问
.antMatchers("/admin-api").access("REQUIRES_SECURE_CHANNEL")
//访问/actuator,不受安全通道限制
.antMatchers("/actuator").access("ANY_CHANNEL");
}
WebAsyncManagerIntegrationFilter
WebAsyncManagerIntegrationFilter主要是用于集成WebAsyncManager,支持在异步线程中管理SecurityContext。
SecurityContextPersistenceFilterSecurityContextPersistenceFilter主要实现了对SecurityContext的在Session中的持久化和存取功能。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
//HttpSessionSecurityContextRepository 从Session中获取SecurityContext
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
try {
//把SecurityContext注入到SecurityContextHolder,SecurityContextHolder中三种SecurityContextHolderStrategy策略,详见下文。
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
//删除线程中的SecurityContext
SecurityContextHolder.clearContext();
//持久化到SecurityContextRepository
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
}
}
默认提供了3种对SecurityContext线程持有策略,可以通过设置系统参数spring.security.strategy来实现切换:
- MODE_THREADLOCAL: 只能获取当前线程的SecurityContext。
- MODE_INHERITABLETHREADLOCAL: 能够获取父线程和当前线程的SecurityContext。
- MODE_GLOBAL: 能够获全局应用的SecurityContext。
HeaderWriterFilter通过一系列HeaderWriter,往HttpServletResponse的Http Header中写入值。可以通过设置shouldWriteHeadersEagerly,来控制前置写入还是后置写入。
CorsFilterCorsFilter通过配置CorsConfiguration,设置CORS Http Header。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
//从CorsConfigurationSource中获取CorsConfiguration
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
//CorsProcessor通过CorsConfiguration对Response进行设置。
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
//如果处理不成功或者当前请求是一个CORS pre-flight,则阻止当前请求继续执行。
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return;
}
filterChain.doFilter(request, response);
}
CsrfFilter
CsrfFilter用于防止CSRF攻击。
CSRF 英文全称是 Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事
Spring Security 防止CSRF攻击的方法:
- 在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中。
- 在浏览器端如果要发起转账的请求,那么需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到 CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
//从Token库中获取CsrfToken(支持CsrfToke持久化在cookie、session或自定义库的中)
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = (csrfToken == null);
if (missingToken) {
//第一次请求需要生成一个CsrfToken,并持久化到Token库。
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
+ this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
: new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, exception);
return;
}
filterChain.doFilter(request, response);
}
配置用法举例:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//访问/admin-api,需要以https开头的方式访问
.antMatchers("/admin-api").access("REQUIRES_SECURE_CHANNEL")
//访问/actuator,不受安全通道限制
.antMatchers("/actuator").access("ANY_CHANNEL");
}
LogoutFilter
CsrfFilter处理登出请求。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//匹配是否登出请求
if (requiresLogout(request, response)) {
//获取登录认证
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Logging out [%s]", auth));
}
//LogoutHandler处理登出操作
this.handler.logout(request, response, auth);
//LogoutSuccessHandler处理成功登出后的操作
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
配置用法举例:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.antMatchers("
}
catch (AuthenticationException authenticationException) {
//登录失败处理,主要做的是清空Cookie
rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response,
authenticationException);
}
}
chain.doFilter(request, response);
}
else {
chain.doFilter(request, response);
}
}
再看下RememberMeAuthenticationFilter::doFilter中的核心逻辑方法RememberMeServices::autoLogin
@Override
public final Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response) {
//提取Cookie中的信息
String rememberMeCookie = extractRememberMeCookie(request);
UserDetails user = null;
try {
//解析信息,获取一个UserDetails
String[] cookieTokens = decodeCookie(rememberMeCookie);
user = processAutoLoginCookie(cookieTokens, request, response);
userDetailsChecker.check(user);
logger.debug("Remember-me cookie accepted");
//通过UserDetails生成一个RememberMeAuthenticationToken
return createSuccessfulAuthentication(request, user);
}
catch (CookieTheftException cte) {
}
cancelCookie(request, response);
return null;
}
AnonymousAuthenticationFilter
AnonymousAuthenticationFilter对未认证的请求,会封装成一个匿名AnonymousAuthenticationToken。
SessionManagementFilterSessionManagementFilter主要是对Session相关的管理,例如,防止会话固定攻击Session Fixation Attack,支持多端登录踢下线等功能。
ExceptionTranslationFilterExceptionTranslationFilter 用于处理 AccessDeniedException 和 AuthenticationException的异常。
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
FilterSecurityInterceptor
FilterSecurityInterceptor主要负责授权Authorization任务。
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
//主要是通过AccessDecisionManager::decide 来进行授权工作。
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
//把Token中的SecurityContext去刷新SecurityContextHolder中的SecurityContext
super.finallyInvocation(token);
}
//用AfterInvocationManager::decide方法进行后置授权工作。
super.afterInvocation(token, null);
}
}



