springsecurity4filter顺序及作⽤
Spring Security 有两个作⽤:认证和授权
⼀、Srping security 4 filter 别名及顺序
spring security 4 标准filter别名和顺序,因为经常要⽤就保存到⾃⼰博客吧
Table 6.1. Standard Filter Aliases and Ordering
Alias Filter Class Namespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
HEADERS_FILTER HeaderWriterFilter http/headers
CSRF_FILTER CsrfFilter http/csrf
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AbstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareRequestFilter http/@servlet-api-provision
JAAS_API_SUPPORT_FILTER JaasApiIntegrationFilter http/@jaas-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A
⼆、Spring security filter作⽤
2.1 默认filter链
在程序启动时会打印出如下⽇志,该⽇志打印出了默认的filter链和顺序,其中SecurityContextPersistenceFilter为第⼀个filter,FilterSecurityInterceptor为最后⼀个filter。
2018-02-11 15:24:17,204  INFO DefaultSecurityFilterChain - Creating filter chain:
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[org.springframework.t.SecurityContextPersistenceFilter@3cf3957d,
org.springframework.t.request.async.WebAsyncManagerIntegrationFilter@7ff34bd,
org.springframework.security.web.header.HeaderWriterFilter@4dad11a2,
org.springframework.security.web.authentication.logout.LogoutFilter@5be6ee89,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@5426eed3,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5da2a66c,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@23169e35,
org.springframework.security.web.session.SessionManagementFilter@5b1627ea,
org.springframework.security.web.access.ExceptionTranslationFilter@70b913f5,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2dfe7327]
2.2 默认filter链作⽤
默认有10条过滤链,下⾯逐个看下去。
2.2.1 /index.html at position 1 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' SecurityContextPersistenceFilter 两个主要职责:
a.请求到来时,通过HttpSessionSecurityContextRepository接⼝从Session中读取SecurityContext,如果读取结果为null,则创建之。
1public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
2        HttpServletRequest request = Request();
3        HttpServletResponse response = Response();
4        HttpSession httpSession = Session(false);
5// 从session中获取SecurityContext
6        SecurityContext context = readSecurityContextFromSession(httpSession);
7
8if (context == null) {
9if (logger.isDebugEnabled()) {
10                logger.debug("No SecurityContext was available from the HttpSession: "
11                        + httpSession + ". " + "A new one will be created.");
12            }
13// 未读取到SecurityContext则新建⼀个SecurityContext
14            context = generateNewContext();
15
16        }
17
18        SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(
19                response, request, httpSession != null, context);
20        requestResponseHolder.setResponse(wrappedResponse);
21
22if (isServlet3) {
23            requestResponseHolder.setRequest(new Servlet3SaveToSessionRequestWrapper(
24                    request, wrappedResponse));
25        }
26
27return context;
28    }
获得SecurityContext之后,会将其存⼊SecurityContextHolder,其中SecurityContextHolder默认是ThreadLocalSecurityContextHolderStrategy实例 1private static void initialize() {
2if ((strategyName == null) || "".equals(strategyName)) {
3// Set default
4            strategyName = MODE_THREADLOCAL;
5        }
6
7if (strategyName.equals(MODE_THREADLOCAL)) {
8            strategy = new ThreadLocalSecurityContextHolderStrategy();
9        }
10// 以下内容省略
11    }
ThreadLocalSecurityContextHolderStrategy中的ContextHolder定义如下,注意这是⼀个ThreadLocal变量,线程局部变量。
1private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
b.请求结束时清空SecurityContextHolder,并将SecurityContext保存到Session中。
1finally {
2            SecurityContext contextAfterChainExecution = SecurityContextHolder
3                    .getContext();
4// Crucial removal of SecurityContextHolder contents - do this before anything
5// else.
6            SecurityContextHolder.clearContext();
7            repo.saveContext(contextAfterChainExecution, Request(),
8                    Response());
9            veAttribute(FILTER_APPLIED);
10
11if (debug) {
12                logger.debug("SecurityContextHolder now cleared, as request processing completed");
13            }
14        }
2.2.2 /index.html at position 2 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
提供了对securityContext和WebAsyncManager的集成,其会把SecurityContext设置到异步线程中,使其也能获取到⽤户上下⽂认证信息。
1 @Override
2protected void doFilterInternal(HttpServletRequest request,
3            HttpServletResponse response, FilterChain filterChain)
4throws ServletException, IOException {
5        WebAsyncManager asyncManager = AsyncManager(request);
6
7        SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
8                .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
9if (securityProcessingInterceptor == null) {
10            isterCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
11new SecurityContextCallableProcessingInterceptor());
12        }
13
14        filterChain.doFilter(request, response);
15    }
2.2.3 /index.html at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
⽤来给http response添加⼀些Header,⽐如X-Frame-Options、X-XSS-Protection*、X-Content-Type-Options。
1protected void doFilterInternal(HttpServletRequest request,
2            HttpServletResponse response, FilterChain filterChain)
3throws ServletException, IOException {
4
5        HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
6                response, this.headerWriters);
7try {
8            filterChain.doFilter(request, headerWriterResponse);
9        }
10finally {
11// 向response header中添加header
12            headerWriterResponse.writeHeaders();
13        }
14    }
2.2.4 /index.html at position 4 of 10 in additional filter chain; firing Filter: 'LogoutFilter'
处理退出登录的Filter,如果请求的url为/logout则会执⾏退出登录操作。
1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
2throws IOException, ServletException {
3        HttpServletRequest request = (HttpServletRequest) req;
4        HttpServletResponse response = (HttpServletResponse) res;
5// 判断是否需要logout,判断request url是否匹配/logout
6if (requiresLogout(request, response)) {
7            Authentication auth = Context().getAuthentication();
8
9if (logger.isDebugEnabled()) {
10                logger.debug("Logging out user '" + auth
11                        + "' and transferring to logout destination");
12            }
13// 执⾏⼀系列的退出登录操作
14for (LogoutHandler handler : handlers) {
15                handler.logout(request, response, auth);
16            }
17// 退出成功,执⾏logoutSuccessHandler进⾏重定向等操作
18            LogoutSuccess(request, response, auth);
19
20return;
21        }
22
23        chain.doFilter(request, response);
24    }
2.2.5 /index.html at position 5 of 10 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'表单认证是最常⽤的⼀个认证⽅式,⼀个最直观的业务场景便是允许⽤户在表单中输⼊⽤户名和密码进⾏登录,⽽这背后的UsernamePasswordAuthenticationFilter,在整个Spring Security的认证体系中则扮演着⾄关重要的⾓⾊。UsernamePasswordAuthenticationFilter是继承⾃AbstractAuthenticationProcessingFilter。
1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
2throws IOException, ServletException {
3
4        HttpServletRequest request = (HttpServletRequest) req;
5        HttpServletResponse response = (HttpServletResponse) res;
6// 判断是否需要执⾏登录认证,判断request url 是否能匹配/login
7if (!requiresAuthentication(request, response)) {
8            chain.doFilter(request, response);
9
10return;
11        }
12
13if (logger.isDebugEnabled()) {
14            logger.debug("Request is to process authentication");
15        }
16
17        Authentication authResult;
18
19try {
20// UsernamePasswordAuthenticationFilter 实现该⽅法
21            authResult = attemptAuthentication(request, response);
22if (authResult == null) {
23// ⼦类未完成认证,⽴即返回
24return;
25            }
26            Authentication(authResult, request, response);
27        }
28// 在认证过程中抛出异常
29catch (InternalAuthenticationServiceException failed) {
30            (
31                    "An internal error occurred while trying to authenticate the user.",
32                    failed);
33            unsuccessfulAuthentication(request, response, failed);
34
35return;
36        }
37catch (AuthenticationException failed) {
38// Authentication failed
39            unsuccessfulAuthentication(request, response, failed);
40
41return;
42        }
43
44// Authentication success
45if (continueChainBeforeSuccessfulAuthentication) {
46            chain.doFilter(request, response);
47        }
48
49        successfulAuthentication(request, response, chain, authResult);
50    }
在UsernamePasswordAuthenticationFilter中实现了类attemptAuthentication,不过该类只实现了⼀个⾮常简化的版本,如果真的需要通过表单登录,是需要⾃⼰继承UsernamePasswordAuthenticationFilter并重载attemptAuthentication⽅法的。
在AbstractAuthenticationProcessingFilter的doFilter⽅法中⼀开始是判断是否有必要进⼊到认证filter,这个过程其实是判断request url是否匹配/login,当然也可以通过filterProcessesUrl属性去配置匹配所使⽤的pattern。
2.2.6 /index.html at position 6 of 10 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
将request存到session中,⽤于缓存request请求,可以⽤于恢复被登录⽽打断的请求
1public void doFilter(ServletRequest request, ServletResponse response,
2            FilterChain chain) throws IOException, ServletException {
3// 从session中获取与当前request匹配的缓存request,并将缓存request从session删除
4        HttpServletRequest wrappedSavedRequest = MatchingRequest(
5                (HttpServletRequest) request, (HttpServletResponse) response);
6// 如果requestCache中缓存了request,则使⽤缓存的request
7        chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
8                response);
9    }
此处从session中取出request,存储request是在ExceptionTranslationFilter中。具体可以参考
2.2.7 /index.html at position 7 of 10 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
此过滤器对ServletRequest进⾏了⼀次包装,使得request具有更加丰富的API
1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
2throws IOException, ServletException {
3        chain.ate((HttpServletRequest) req,
4                (HttpServletResponse) res), res);
5    }
2.2.8 /index.html at position 8 of 10 in additional filter chain; firing Filter: 'SessionManagementFilter'
和session相关的过滤器,内部维护了⼀个SessionAuthenticationStrategy,两者组合使⽤,常⽤来防⽌session-fixation protection attack,以及限制同⼀⽤户开启多个会话的数量
与登录认证拦截时作⽤⼀样,持久化⽤户登录信息,可以保存到session中,也可以保存到cookie或者re
dis中。
1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
2throws IOException, ServletException {
3        HttpServletRequest request = (HttpServletRequest) req;
4        HttpServletResponse response = (HttpServletResponse) res;
5
6if (Attribute(FILTER_APPLIED) != null) {
7            chain.doFilter(request, response);
8return;
9        }
10
11        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
12
13if (!ainsContext(request)) {
14            Authentication authentication = Context()
15                    .getAuthentication();
16
17if (authentication != null && !trustResolver.isAnonymous(authentication)) {
18// The user has been authenticated during the current request, so call the
19// session strategy
20try {
21                    Authentication(authentication,
22                            request, response);
23                }
24catch (SessionAuthenticationException e) {
25// The session strategy can reject the authentication
26                    logger.debug(
27                            "SessionAuthenticationStrategy rejected the authentication object",
28                            e);
29                    SecurityContextHolder.clearContext();
30                    AuthenticationFailure(request, response, e);
31
32return;
33                }
34// Eagerly save the security context to make it available for any possible
35// re-entrant
36// requests which may occur before the current request completes.
37// SEC-1396.
38                securityContextRepository.Context(),
39                        request, response);
40            }
41else {
42// No security context or authentication present. Check for a session
43// timeout
spring framework版本
44if (RequestedSessionId() != null
45                        && !request.isRequestedSessionIdValid()) {
46if (logger.isDebugEnabled()) {
47                        logger.debug("Requested session ID "
48                                + RequestedSessionId() + " is invalid.");
49                    }
50
51if (invalidSessionStrategy != null) {
52                        invalidSessionStrategy
53                                .onInvalidSessionDetected(request, response);
54return;
55                    }
56                }
57            }
58        }
59
60        chain.doFilter(request, response);
61    }
2.2.9 /index.html at position 9 of 10 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
异常拦截,其处在Filter链后部分,只能拦截其后⾯的节点并且只处理AuthenticationException与AccessDeniedException两个异常。
AuthenticationException指的是未登录状态下访问受保护资源,AccessDeniedException指的是登陆了但是由于权限不⾜(⽐如普通⽤户访问管理员界⾯)。
1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
2throws IOException, ServletException {
3        HttpServletRequest request = (HttpServletRequest) req;
4        HttpServletResponse response = (HttpServletResponse) res;
5
6try {
7// 直接执⾏后⾯的filter,并捕获异常
8            chain.doFilter(request, response);
9
10            logger.debug("Chain processed normally");
11        }
12catch (IOException ex) {
13throw ex;
14        }
15catch (Exception ex) {
16// 从异常堆栈中提取SpringSecurityException
17            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
18            RuntimeException ase = (AuthenticationException) throwableAnalyzer
19                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);
20
21if (ase == null) {
22                ase = (AccessDeniedException) FirstThrowableOfType(
23                        AccessDeniedException.class, causeChain);
24            }
25
26if (ase != null) {
27// 处理异常
28                handleSpringSecurityException(request, response, chain, ase);
29            }
30else {
31// Rethrow ServletExceptions and RuntimeExceptions as-is
32if (ex instanceof ServletException) {
33throw (ServletException) ex;
34                }
35else if (ex instanceof RuntimeException) {
36throw (RuntimeException) ex;
37                }
38
39// Wrap other Exceptions. This shouldn't actually happen
40// as we've already covered all the possibilities for doFilter
41throw new RuntimeException(ex);
42            }
43        }
44    }
在这个catch代码中通过从异常堆栈中捕获到Throwable[],然后通过handleSpringSecurityException⽅法处理异常,在该⽅法中只会去处理AuthenticationException 和AccessDeniedException异常。
1private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { 2
3if (exception instanceof AuthenticationException) {
4// 认证异常,由sendStartAuthentication⽅法发起认证过程
5            logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
6            sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
7        } else if (exception instanceof AccessDeniedException) {
8// 访问权限异常
9if (authenticationTrustResolver.Context().getAuthentication())) {
10                logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point", exception);
11// 匿名⽤户重定向到认证⼊⼝点执⾏认证过程
12                sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException("Full authentication is required to access this resource"));
13            } else {
14// 拒绝访问,由accessDeniedHandler处理,response 403
15                logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
16                accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
17            }
18        }
19    }

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。