SpringSecurity过滤器顺序
官网定义的关键过滤器顺序:
源码里所有的过滤器顺序:
默认情况会启动以下过滤器:
(默认设置在:WebSecurityConfigurerAdapter 的 getHttp()方法里)
怎么替换默认的过滤器:
http.addFilterAt() 不能替换默认的过滤器,只是在相同的位置放置一个过滤器,原本的过滤器仍然起作用
可以disable掉默认的过滤器,例如用自定义的登出过滤器
http.logout().disable();
http.addFilterAt(new MyLogoutFilter(), LogoutFilter.class);
部分过滤器的含义:
ChannelProcessingFilter:转换协议时使用,例如将http重定向到https
ConcurrentSessionFilter:判断session是否过期以及更新最新访问时间
SecurityContextPersistenceFilter:将用户信息绑定到线程
这样全局可通过SecurityContextHolder.getContext().getAuthentication()拿到用户信息
注:如果要拿到request/response信息(这个不是过滤器设置的,是框架默认会绑定到线程)
可通过((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()
HeaderWriterFilter:默认增加以下头部信息
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY关闭:http.headers().disable();
只保留缓存控制:http.headers().defaultsDisabled().cacheControl()
注:详细描述可查看官网地址
X-Content-Type-Options: nosniff 表示浏览器必须且只能根据Content-Type字段分辨资源类型(浏览器默认会 猜测资源类型)
X-XSS-Protection:防范XSS攻击
- 0:禁用XSS保护;
- 1:启用XSS保护;
- 1; mode=block:启用XSS保护,并在检查到XSS攻击时,停止渲染页面(例如IE8中,检查到攻击时,整个页面会被一个#替换);
X-Frame-Options:是否允许页面被嵌套
DENY 表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许
SAMEORIGIN 表示该页面可以在相同域名页面的 frame 中展示
ALLOW-FROM uri 表示该页面可以在指定来源的 frame 中展示Cache-Control/Pragma/Expires:缓存控制(简单描述)
CorsFilter: 配置跨域
-
-
protected void configure(HttpSecurity http) throws Exception {
-
http
-
// by default uses a Bean by the name of corsConfigurationSource
-
.cors().and()
-
...
-
}
-
-
-
CorsConfigurationSource corsConfigurationSource() {
-
CorsConfiguration configuration = new CorsConfiguration();
-
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
-
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
-
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-
source.registerCorsConfiguration("/**", configuration);
-
return source;
-
}
CsrfFilter: 防止Csrf攻击,通过配置与Session绑定的token,每次请求都需要携带最新的token
注:默认是在前后端不分离的情况下,通过jsp/ft等传递到前端。在前后端分离的情况下,可以增加过滤器使其配置在 json/或者头部
LogoutFilter:配置登出的处理 一般可设置登出成功的处理,删除cookie,使Session无效等
UsernamePasswordAuthenticationFilter:验证登录,并可以配置登录成功/失败的处理
-
http.formLogin() // 表单登录
-
// 登录的界面,前后端分离时不需要这个配置。没登录去访问系统资源时会重定向到这个界面
-
.loginPage("/login/home")
-
// 登录验证,框架自动实现
-
.loginProcessingUrl("/login/verify")
-
// 如果直接访问的是登录界面login1,返回的URL,否则会返回重定向到原本请求的URL
-
.successHandler(myAuthenticationSuccessHandler)
-
// 失败返回的URL
-
.failureHandler(myAuthenticationFailureHandler)
SecurityContextHolderAwareRequestFilter: 包装类,实现HttpServletRequest的getAuthentication getRemoteUser等方法
RememberMeAuthenticationFilter: 配置rememberMe,比如7天之内不需要登录
当拿不到用户信息时(SecurityContextHolder.getContext()为空),会去找key是remember-me的Cookie配置用户信息
AnonymousAuthenticationFilter:没有通过username和remember认证的用户赋予匿名身份
SessionManagementFilter:session管理:限制同一用户开启多个会话的数量
ExceptionTranslationFilter:一般其只处理两大类异常:
AccessDeniedException访问权限异常
AuthenticationException用户认证异常:包括匿名用户异常
配置自定义的401和403异常处理:
-
http.exceptionHandling()
-
.accessDeniedHandler(new MyAccessDeniedHandler())
-
.authenticationEntryPoint(new MyAuthenticationEntryPoint());
FilterSecurityInterceptor :拿到用户的权限(从SecurityContextHolder中获取Authentication对象)和资源所需权限(SecurityMetadataSource),在AccessDecisionManager里对比看用户是否有权限
注:
CsrfFilter类
-
-
protected void doFilterInternal(HttpServletRequest request,
-
HttpServletResponse response, FilterChain filterChain)
-
throws ServletException, IOException {
-
request.setAttribute(HttpServletResponse.class.getName(), response);
-
// 获取与session绑定的csrfToken,没有就新建一个
-
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
-
final boolean missingToken = csrfToken == null;
-
if (missingToken) {
-
csrfToken = this.tokenRepository.generateToken(request);
-
this.tokenRepository.saveToken(csrfToken, request, response);
-
}
-
-
request.setAttribute(CsrfToken.class.getName(), csrfToken);
-
request.setAttribute(csrfToken.getParameterName(), csrfToken);
-
-
// 如果不是与csrf相关的请求就直接跳过
-
if (!this.requireCsrfProtectionMatcher.matches(request)) {
-
filterChain.doFilter(request, response);
-
return;
-
}
-
// 否则直接比对 头部/表单里的csrfToken 和 session绑定的CsrfToken
-
String actualToken = request.getHeader(csrfToken.getHeaderName());
-
if (actualToken == null) {
-
actualToken = request.getParameter(csrfToken.getParameterName());
-
}
-
if (!csrfToken.getToken().equals(actualToken)) {
-
if (this.logger.isDebugEnabled()) {
-
this.logger.debug("Invalid CSRF token found for "
-
+ UrlUtils.buildFullRequestUrl(request));
-
}
-
if (missingToken) {
-
this.accessDeniedHandler.handle(request, response,
-
new MissingCsrfTokenException(actualToken));
-
}
-
else {
-
this.accessDeniedHandler.handle(request, response,
-
new InvalidCsrfTokenException(csrfToken, actualToken));
-
}
-
return;
-
}
-
-
filterChain.doFilter(request, response);
-
}