目录
一、Filter Chain 图解
二、过滤器逐一解析
2.1.WebAsyncManagerIntegrationFilter
2.2.SecurityContextPersistenceFilter
2.3.HeaderWriterFilter
2.4.CsrfFilter
2.5.LogoutFilter
2.6.RequestCacheAwareFilter
2.7.SecurityContextHolderAwareRequestFilter
2.8.AnonymousAuthenticationFilter
2.9.SessionManagementFilter
2.10.ExceptionTranslationFilter
2.11.FilterSecurityInterceptor
2.12.UsernamePasswordAuthenticationFilter
前言:

在熟悉Spring Security的使用和基本操作后,有时根据项目需求,我们需要在security原有的过滤器链中,添加符合我们自己的过滤器来实现功能时,我们就必须得先了解security的核心过滤链的流程和每个过滤器的各自功能,以此,我们才可以在特点的过滤器前后加入属于我们项目需求的过滤器。

一、Filter Chain 图解
在配置了spring security了之后,会在运行项目的时候,DefaultSecurityFilterChain会输出相关log:

  1. public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters){
  2. logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
  3. this.requestMatcher = requestMatcher;
  4. this.filters = new ArrayList<Filter>(filters);
  5. }

输出以下Log:

[main] o.s.s.web.DefaultSecurityFilterChain :
Creating filter chain:
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@184de357,
org.springframework.security.web.context.SecurityContextPersistenceFilter@521ba38f,
org.springframework.security.web.header.HeaderWriterFilter@77bb916f,
org.springframework.security.web.csrf.CsrfFilter@76b305e1,
org.springframework.security.web.authentication.logout.LogoutFilter@17c53dfb,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2086d469,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@b1d19ff,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@efe49ab,
org.springframework.security.web.session.SessionManagementFilter@5a48d186,
org.springframework.security.web.access.ExceptionTranslationFilter@273aaab7
]
也可以从Debug进行查看:

二、过滤器逐一解析
在解析前,先说说两个至关重要的类:OncePerRequestFilter和GenericFilterBean,在过滤器链的过滤器中,或多或少间接或直接继承到

OncePerRequestFilter顾名思义,能够确保在一次请求只通过一次filter,而不需要重复执行。
GenericFilterBean是javax.servlet.Filter接口的一个基本的实现类
GenericFilterBean将web.xml中filter标签中的配置参数-init-param项作为bean的属性
GenericFilterBean可以简单地成为任何类型的filter的父类
GenericFilterBean的子类可以自定义一些自己需要的属性
GenericFilterBean,将实际的过滤工作留给他的子类来完成,这就导致了他的子类不得不实现doFilter方法
GenericFilterBean不依赖于Spring的ApplicationContext,Filters通常不会直接读取他们的容器信息(ApplicationContext concept)而是通过访问spring容器(Spring root application context)中的service beans来获取,通常是通过调用filter里面的getServletContext() 方法来获取

2.1.WebAsyncManagerIntegrationFilter

  1. public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
  2. ......
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request,
  5. HttpServletResponse response, FilterChain filterChain)
  6. throws ServletException, IOException {
  7. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  8. SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
  9. .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
  10. if (securityProcessingInterceptor == null) {
  11. asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
  12. new SecurityContextCallableProcessingInterceptor());
  13. }
  14. filterChain.doFilter(request, response);
  15. }
  16. }

从源码中,我们可以分析出WebAsyncManagerIntegrationFilter相关功能:

根据请求封装获取WebAsyncManager
从WebAsyncManager获取/注册SecurityContextCallableProcessingInterceptor

2.2.SecurityContextPersistenceFilter

  1. public class SecurityContextPersistenceFilter extends GenericFilterBean {
  2. ......
  3. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  4. throws IOException, ServletException {
  5. HttpServletRequest request = (HttpServletRequest) req;
  6. HttpServletResponse response = (HttpServletResponse) res;
  7. if (request.getAttribute(FILTER_APPLIED) != null) {
  8. // ensure that filter is only applied once per request
  9. chain.doFilter(request, response);
  10. return;
  11. }
  12. final boolean debug = logger.isDebugEnabled();
  13. request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
  14. if (forceEagerSessionCreation) {
  15. HttpSession session = request.getSession();
  16. if (debug && session.isNew()) {
  17. logger.debug("Eagerly created session: " + session.getId());
  18. }
  19. }
  20. HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
  21. response);
  22. SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
  23. try {
  24. SecurityContextHolder.setContext(contextBeforeChainExecution);
  25. chain.doFilter(holder.getRequest(), holder.getResponse());
  26. }
  27. finally {
  28. SecurityContext contextAfterChainExecution = SecurityContextHolder
  29. .getContext();
  30. // Crucial removal of SecurityContextHolder contents - do this before anything
  31. // else.
  32. SecurityContextHolder.clearContext();
  33. repo.saveContext(contextAfterChainExecution, holder.getRequest(),
  34. holder.getResponse());
  35. request.removeAttribute(FILTER_APPLIED);
  36. if (debug) {
  37. logger.debug("SecurityContextHolder now cleared, as request processing completed");
  38. }
  39. }
  40. }
  41. ......
  42. }

从源码中,我们可以分析出SecurityContextPersistenceFilter相关功能:

先实例SecurityContextHolder->HttpSessionSecurityContextRepository(下面以repo代替).作用:其会从Session中取出已认证用户的信息,提高效率,避免每一次请求都要查询用户认证信息。
根据请求和响应构建HttpRequestResponseHolder
repo根据HttpRequestResponseHolder加载context获取SecurityContext
SecurityContextHolder将获得到的SecurityContext设置到Context中,然后继续向下执行其他过滤器
finally-> SecurityContextHolder获取SecurityContext,然后清除,并将其和请求信息保存到repo,从请求中移除FILTER_APPLIED属性

2.3.HeaderWriterFilter

  1. public class HeaderWriterFilter extends OncePerRequestFilter {
  2. ......
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request,
  5. HttpServletResponse response, FilterChain filterChain)
  6. throws ServletException, IOException {
  7. for (HeaderWriter headerWriter : headerWriters) {
  8. headerWriter.writeHeaders(request, response);
  9. }
  10. filterChain.doFilter(request, response);
  11. }
  12. }

从源码中,我们可以分析HeaderWriterFilter相关功能:

往该请求的Header中添加相应的信息,在http标签内部使用security:headers来控制

2.4.CsrfFilter

  1. public final class CsrfFilter extends OncePerRequestFilter {
  2. ......
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request,
  5. HttpServletResponse response, FilterChain filterChain)
  6. throws ServletException, IOException {
  7. request.setAttribute(HttpServletResponse.class.getName(), response);
  8. CsrfToken csrfToken = this.tokenRepository.loadToken(request);
  9. final boolean missingToken = csrfToken == null;
  10. if (missingToken) {
  11. csrfToken = this.tokenRepository.generateToken(request);
  12. this.tokenRepository.saveToken(csrfToken, request, response);
  13. }
  14. request.setAttribute(CsrfToken.class.getName(), csrfToken);
  15. request.setAttribute(csrfToken.getParameterName(), csrfToken);
  16. if (!this.requireCsrfProtectionMatcher.matches(request)) {
  17. filterChain.doFilter(request, response);
  18. return;
  19. }
  20. String actualToken = request.getHeader(csrfToken.getHeaderName());
  21. if (actualToken == null) {
  22. actualToken = request.getParameter(csrfToken.getParameterName());
  23. }
  24. if (!csrfToken.getToken().equals(actualToken)) {
  25. if (this.logger.isDebugEnabled()) {
  26. this.logger.debug("Invalid CSRF token found for "
  27. + UrlUtils.buildFullRequestUrl(request));
  28. }
  29. if (missingToken) {
  30. this.accessDeniedHandler.handle(request, response,
  31. new MissingCsrfTokenException(actualToken));
  32. }
  33. else {
  34. this.accessDeniedHandler.handle(request, response,
  35. new InvalidCsrfTokenException(csrfToken, actualToken));
  36. }
  37. return;
  38. }
  39. filterChain.doFilter(request, response);
  40. }
  41. ......
  42. }

从源码中,我们可以分析出CsrfFilter相关功能:

csrf又称跨域请求伪造,攻击方通过伪造用户请求访问受信任站点。
对需要验证的请求验证是否包含csrf的token信息,如果不包含,则报错。这样攻击网站无法获取到token信息,则跨域提交的信息都无法通过过滤器的校验。

2.5.LogoutFilter

  1. public class LogoutFilter extends GenericFilterBean {
  2. ......
  3. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  4. throws IOException, ServletException {
  5. HttpServletRequest request = (HttpServletRequest) req;
  6. HttpServletResponse response = (HttpServletResponse) res;
  7. if (requiresLogout(request, response)) {
  8. Authentication auth = SecurityContextHolder.getContext().getAuthentication();
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Logging out user '" + auth
  11. + "' and transferring to logout destination");
  12. }
  13. this.handler.logout(request, response, auth);
  14. logoutSuccessHandler.onLogoutSuccess(request, response, auth);
  15. return;
  16. }
  17. chain.doFilter(request, response);
  18. }
  19. ......
  20. }

从源码中,我们可以分析出LogoutFilter相关功能:
匹配URL,默认为/logout
匹配成功后则用户退出,清除认证信息
2.6.RequestCacheAwareFilter

  1. public class RequestCacheAwareFilter extends GenericFilterBean {
  2. ......
  3. public void doFilter(ServletRequest request, ServletResponse response,
  4. FilterChain chain) throws IOException, ServletException {
  5. HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
  6. (HttpServletRequest) request, (HttpServletResponse) response);
  7. chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
  8. response);
  9. }
  10. }

从源码中,我们可以分析出RequestCacheAwareFilter相关功能:

通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest

2.7.SecurityContextHolderAwareRequestFilter

  1. public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
  2. ......
  3. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  4. throws IOException, ServletException {
  5. chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
  6. (HttpServletResponse) res), res);
  7. }
  8. ......
  9. }

从源码中,我们可以分析出SecurityContextHolderAwareRequestFilter相关功能:

针对ServletRequest进行了一次包装,使得request具有更加丰富的API

2.8.AnonymousAuthenticationFilter

  1. public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {
  2. ......
  3. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  4. throws IOException, ServletException {
  5. if (SecurityContextHolder.getContext().getAuthentication() == null) {
  6. SecurityContextHolder.getContext().setAuthentication(
  7. createAuthentication((HttpServletRequest) req));
  8. if (logger.isDebugEnabled()) {
  9. logger.debug("Populated SecurityContextHolder with anonymous token: '"
  10. + SecurityContextHolder.getContext().getAuthentication() + "'");
  11. }
  12. }
  13. else {
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
  16. + SecurityContextHolder.getContext().getAuthentication() + "'");
  17. }
  18. }
  19. chain.doFilter(req, res);
  20. }
  21. ......
  22. }

从源码中,我们可以分析出AnonymousAuthenticationFilter相关功能:

当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。匿名身份过滤器,这个过滤器个人认为很重要,需要将它与UsernamePasswordAuthenticationFilter 放在一起比较理解,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
匿名认证过滤器,可能有人会想:匿名了还有身份?个人对于Anonymous匿名身份的理解是Spirng Security为了整体逻辑的统一性,即使是未通过认证的用户,也给予了一个匿名身份。而AnonymousAuthenticationFilter该过滤器的位置也是非常的科学的,它位于常用的身份认证过滤器(如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter)之后,意味着只有在上述身份过滤器执行完毕后,SecurityContext依旧没有用户信息,AnonymousAuthenticationFilter该过滤器才会有意义—-基于用户一个匿名身份。

2.9.SessionManagementFilter

  1. public class SessionManagementFilter extends GenericFilterBean {
  2. ......
  3. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  4. throws IOException, ServletException {
  5. HttpServletRequest request = (HttpServletRequest) req;
  6. HttpServletResponse response = (HttpServletResponse) res;
  7. if (request.getAttribute(FILTER_APPLIED) != null) {
  8. chain.doFilter(request, response);
  9. return;
  10. }
  11. request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
  12. if (!securityContextRepository.containsContext(request)) {
  13. Authentication authentication = SecurityContextHolder.getContext()
  14. .getAuthentication();
  15. if (authentication != null && !trustResolver.isAnonymous(authentication)) {
  16. // The user has been authenticated during the current request, so call the
  17. // session strategy
  18. try {
  19. sessionAuthenticationStrategy.onAuthentication(authentication,
  20. request, response);
  21. }
  22. catch (SessionAuthenticationException e) {
  23. // The session strategy can reject the authentication
  24. logger.debug(
  25. "SessionAuthenticationStrategy rejected the authentication object",
  26. e);
  27. SecurityContextHolder.clearContext();
  28. failureHandler.onAuthenticationFailure(request, response, e);
  29. return;
  30. }
  31. // Eagerly save the security context to make it available for any possible
  32. // re-entrant
  33. // requests which may occur before the current request completes.
  34. // SEC-1396.
  35. securityContextRepository.saveContext(SecurityContextHolder.getContext(),
  36. request, response);
  37. }
  38. else {
  39. // No security context or authentication present. Check for a session
  40. // timeout
  41. if (request.getRequestedSessionId() != null
  42. && !request.isRequestedSessionIdValid()) {
  43. if (logger.isDebugEnabled()) {
  44. logger.debug("Requested session ID "
  45. + request.getRequestedSessionId() + " is invalid.");
  46. }
  47. if (invalidSessionStrategy != null) {
  48. invalidSessionStrategy
  49. .onInvalidSessionDetected(request, response);
  50. return;
  51. }
  52. }
  53. }
  54. }
  55. chain.doFilter(request, response);
  56. }
  57. ......
  58. }

从源码中,我们可以分析出SessionManagementFilter相关功能:

securityContextRepository限制同一用户开启多个会话的数量
SessionAuthenticationStrategy防止session-fixation protection attack(保护非匿名用户)

2.10.ExceptionTranslationFilter

  1. public class ExceptionTranslationFilter extends GenericFilterBean {
  2. ......
  3. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  4. throws IOException, ServletException {
  5. HttpServletRequest request = (HttpServletRequest) req;
  6. HttpServletResponse response = (HttpServletResponse) res;
  7. try {
  8. chain.doFilter(request, response);
  9. logger.debug("Chain processed normally");
  10. }
  11. catch (IOException ex) {
  12. throw ex;
  13. }
  14. catch (Exception ex) {
  15. // Try to extract a SpringSecurityException from the stacktrace
  16. Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
  17. RuntimeException ase = (AuthenticationException) throwableAnalyzer
  18. .getFirstThrowableOfType(AuthenticationException.class, causeChain);
  19. if (ase == null) {
  20. ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
  21. AccessDeniedException.class, causeChain);
  22. }
  23. if (ase != null) {
  24. handleSpringSecurityException(request, response, chain, ase);
  25. }
  26. else {
  27. // Rethrow ServletExceptions and RuntimeExceptions as-is
  28. if (ex instanceof ServletException) {
  29. throw (ServletException) ex;
  30. }
  31. else if (ex instanceof RuntimeException) {
  32. throw (RuntimeException) ex;
  33. }
  34. // Wrap other Exceptions. This shouldn't actually happen
  35. // as we've already covered all the possibilities for doFilter
  36. throw new RuntimeException(ex);
  37. }
  38. }
  39. }
  40. ......
  41. }

从源码中,我们可以分析出ExceptionTranslationFilter相关功能:

ExceptionTranslationFilter异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常
此过滤器的作用是处理中FilterSecurityInterceptor抛出的异常,然后将请求重定向到对应页面,或返回对应的响应错误代码

2.11.FilterSecurityInterceptor

  1. public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
  2. ......
  3. public void doFilter(ServletRequest request, ServletResponse response,
  4. FilterChain chain) throws IOException, ServletException {
  5. FilterInvocation fi = new FilterInvocation(request, response, chain);
  6. invoke(fi);
  7. }
  8. public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
  9. return this.securityMetadataSource;
  10. }
  11. public SecurityMetadataSource obtainSecurityMetadataSource() {
  12. return this.securityMetadataSource;
  13. }
  14. public void invoke(FilterInvocation fi) throws IOException, ServletException {
  15. if ((fi.getRequest() != null)
  16. && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
  17. && observeOncePerRequest) {
  18. // filter already applied to this request and user wants us to observe
  19. // once-per-request handling, so don't re-do security checking
  20. fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  21. }
  22. else {
  23. // first time this request being called, so perform security checking
  24. if (fi.getRequest() != null) {
  25. fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
  26. }
  27. InterceptorStatusToken token = super.beforeInvocation(fi);
  28. try {
  29. fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  30. }
  31. finally {
  32. super.finallyInvocation(token);
  33. }
  34. super.afterInvocation(token, null);
  35. }
  36. }
  37. ......
  38. }

从源码中,我们可以分析出FilterSecurityInterceptor相关功能:

获取到所配置资源访问的授权信息
根据SecurityContextHolder中存储的用户信息来决定其是否有权限
主要一些实现功能在其父类AbstractSecurityInterceptor中

2.12.UsernamePasswordAuthenticationFilter

  1. public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  2. ......
  3. public Authentication attemptAuthentication(HttpServletRequest request,
  4. HttpServletResponse response) throws AuthenticationException {
  5. if (postOnly && !request.getMethod().equals("POST")) {
  6. throw new AuthenticationServiceException(
  7. "Authentication method not supported: " + request.getMethod());
  8. }
  9. String username = obtainUsername(request);
  10. String password = obtainPassword(request);
  11. if (username == null) {
  12. username = "";
  13. }
  14. if (password == null) {
  15. password = "";
  16. }
  17. username = username.trim();
  18. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
  19. username, password);
  20. // Allow subclasses to set the "details" property
  21. setDetails(request, authRequest);
  22. return this.getAuthenticationManager().authenticate(authRequest);
  23. }
  24. ......
  25. }

从源码中,我们可以分析出UsernamePasswordAuthenticationFilter相关功能:

表单认证是最常用的一个认证方式,一个最直观的业务场景便是允许用户在表单中输入用户名和密码进行登录,而这背后的UsernamePasswordAuthenticationFilter,在整个Spring Security的认证体系中则扮演着至关重要的角色
以上为个人经验,希望能给大家一个参考

更多相关文章

  1. 【Vue框架学习】过滤器、自定义指令、生命周期、动画、组件、路
  2. 04-Vue_过滤器
  3. Servlet过滤器使用实例(防止用户恶意登录)
  4. uni-app,Vue 使用 filter 过滤或者替换 v-for 的值
  5. Shiro源码分析---FilterChain创建过程
  6. Shiro源码分析----认证流程
  7. springboot|springboot配置Filter过滤器
  8. 【Nest教程】为项目增加个自定义过滤器
  9. php常量知识点的总结及过滤器的使用(1118)

随机推荐

  1. SWAP空间不足的处理方法
  2. Linux下编译安装PHP7.2
  3. 为什么“可执行文件”操作系统是独立的?
  4. 一个资源管理系统的设计--解析linux的cgr
  5. 安装mysql 出现:Fatal error: Can&#39;t o
  6. Ubuntu 开机自动挂载磁盘
  7. 如何确保我的Linux程序不会产生核心转储?
  8. Bash脚本不删除给定目录中的文件
  9. 用 S5PV210 学习 Linux (二) 刷机(二)
  10. linux挂载分区后重启失败