本文主要研究下SecurityContextPersistenceFilter的做用。java
spring security內置的各類filter:web
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 |
能夠看到SecurityContextPersistenceFilter優先級僅次於ChannelProcessingFilter
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/SecurityContextPersistenceFilter.javaspring
/** * Populates the {@link SecurityContextHolder} with information obtained from the * configured {@link SecurityContextRepository} prior to the request and stores it back in * the repository once the request has completed and clearing the context holder. By * default it uses an {@link HttpSessionSecurityContextRepository}. See this class for * information <tt>HttpSession</tt> related configuration options. * <p> * This filter will only execute once per request, to resolve servlet container * (specifically Weblogic) incompatibilities. * <p> * This filter MUST be executed BEFORE any authentication processing mechanisms. * Authentication processing mechanisms (e.g. BASIC, CAS processing filters etc) expect * the <code>SecurityContextHolder</code> to contain a valid <code>SecurityContext</code> * by the time they execute. * <p> * This is essentially a refactoring of the old * <tt>HttpSessionContextIntegrationFilter</tt> to delegate the storage issues to a * separate strategy, allowing for more customization in the way the security context is * maintained between requests. * <p> * The <tt>forceEagerSessionCreation</tt> property can be used to ensure that a session is * always available before the filter chain executes (the default is <code>false</code>, * as this is resource intensive and not recommended). * * @author Luke Taylor * @since 3.0 */ public class SecurityContextPersistenceFilter extends GenericFilterBean { static final String FILTER_APPLIED = "__spring_security_scpf_applied"; private SecurityContextRepository repo; private boolean forceEagerSessionCreation = false; public SecurityContextPersistenceFilter() { this(new HttpSessionSecurityContextRepository()); } public SecurityContextPersistenceFilter(SecurityContextRepository repo) { this.repo = repo; } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) { // ensure that filter is only applied once per request chain.doFilter(request, response); return; } final boolean debug = logger.isDebugEnabled(); request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (forceEagerSessionCreation) { HttpSession session = request.getSession(); if (debug && session.isNew()) { logger.debug("Eagerly created session: " + session.getId()); } } HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder .getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything // else. SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); if (debug) { logger.debug("SecurityContextHolder now cleared, as request processing completed"); } } } public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) { this.forceEagerSessionCreation = forceEagerSessionCreation; } }
SecurityContextPersistenceFilter是承接容器的session與spring security的重要filter,主要工做是從session中獲取SecurityContext,而後放到上下文中,以後的filter大多依賴這個來獲取登陸態。其主要是經過HttpSessionSecurityContextRepository來存取的。
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/HttpSessionSecurityContextRepository.javaapi
/** * A {@code SecurityContextRepository} implementation which stores the security context in * the {@code HttpSession} between requests. * <p> * The {@code HttpSession} will be queried to retrieve the {@code SecurityContext} in the * <tt>loadContext</tt> method (using the key {@link #SPRING_SECURITY_CONTEXT_KEY} by * default). If a valid {@code SecurityContext} cannot be obtained from the * {@code HttpSession} for whatever reason, a fresh {@code SecurityContext} will be * created by calling by {@link SecurityContextHolder#createEmptyContext()} and this * instance will be returned instead. * <p> * When <tt>saveContext</tt> is called, the context will be stored under the same key, * provided * <ol> * <li>The value has changed</li> * <li>The configured <tt>AuthenticationTrustResolver</tt> does not report that the * contents represent an anonymous user</li> * </ol> * <p> * With the standard configuration, no {@code HttpSession} will be created during * <tt>loadContext</tt> if one does not already exist. When <tt>saveContext</tt> is called * at the end of the web request, and no session exists, a new {@code HttpSession} will * <b>only</b> be created if the supplied {@code SecurityContext} is not equal to an empty * {@code SecurityContext} instance. This avoids needless <code>HttpSession</code> * creation, but automates the storage of changes made to the context during the request. * Note that if {@link SecurityContextPersistenceFilter} is configured to eagerly create * sessions, then the session-minimisation logic applied here will not make any * difference. If you are using eager session creation, then you should ensure that the * <tt>allowSessionCreation</tt> property of this class is set to <tt>true</tt> (the * default). * <p> * If for whatever reason no {@code HttpSession} should <b>ever</b> be created (for * example, if Basic authentication is being used or similar clients that will never * present the same {@code jsessionid}), then {@link #setAllowSessionCreation(boolean) * allowSessionCreation} should be set to <code>false</code>. Only do this if you really * need to conserve server memory and ensure all classes using the * {@code SecurityContextHolder} are designed to have no persistence of the * {@code SecurityContext} between web requests. * * @author Luke Taylor * @since 3.0 */ public class HttpSessionSecurityContextRepository implements SecurityContextRepository { /** * The default key under which the security context will be stored in the session. */ public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; /** * Gets the security context for the current request (if available) and returns it. * <p> * If the session is null, the context object is null or the context object stored in * the session is not an instance of {@code SecurityContext}, a new context object * will be generated and returned. */ public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { HttpServletRequest request = requestResponseHolder.getRequest(); HttpServletResponse response = requestResponseHolder.getResponse(); HttpSession httpSession = request.getSession(false); SecurityContext context = readSecurityContextFromSession(httpSession); if (context == null) { if (logger.isDebugEnabled()) { logger.debug("No SecurityContext was available from the HttpSession: " + httpSession + ". " + "A new one will be created."); } context = generateNewContext(); } SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper( response, request, httpSession != null, context); requestResponseHolder.setResponse(wrappedResponse); if (isServlet3) { requestResponseHolder.setRequest(new Servlet3SaveToSessionRequestWrapper( request, wrappedResponse)); } return context; } //...... }
/** * * @param httpSession the session obtained from the request. */ private SecurityContext readSecurityContextFromSession(HttpSession httpSession) { final boolean debug = logger.isDebugEnabled(); if (httpSession == null) { if (debug) { logger.debug("No HttpSession currently exists"); } return null; } // Session exists, so try to obtain a context from it. Object contextFromSession = httpSession.getAttribute(springSecurityContextKey); if (contextFromSession == null) { if (debug) { logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT"); } return null; } // We now have the security context object from the session. if (!(contextFromSession instanceof SecurityContext)) { if (logger.isWarnEnabled()) { logger.warn(springSecurityContextKey + " did not contain a SecurityContext but contained: '" + contextFromSession + "'; are you improperly modifying the HttpSession directly " + "(you should always use SecurityContextHolder) or using the HttpSession attribute " + "reserved for this class?"); } return null; } if (debug) { logger.debug("Obtained a valid SecurityContext from " + springSecurityContextKey + ": '" + contextFromSession + "'"); } // Everything OK. The only non-null return from this method. return (SecurityContext) contextFromSession; }
能夠看到是從session中取出名爲SPRING_SECURITY_CONTEXT的attribute
取出來是SecurityContextImpl,實例以下session
org.springframework.security.core.context.SecurityContextImpl@4198818b: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4198818b: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@380f4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 5B9AFCC0537B1202116A23CEC6B41142; Granted Authorities: ROLE_USER
能夠看到,若是是以前登陸過的,則session會關聯上登陸用戶信息,包含用戶的AuthenticationT信息,好比Principal,Granted Authorities等,這些都是重要的鑑權依據。
若是取出來沒有的話,則new一個app
/** * By default, calls {@link SecurityContextHolder#createEmptyContext()} to obtain a * new context (there should be no context present in the holder when this method is * called). Using this approach the context creation strategy is decided by the * {@link SecurityContextHolderStrategy} in use. The default implementations will * return a new <tt>SecurityContextImpl</tt>. * * @return a new SecurityContext instance. Never null. */ protected SecurityContext generateNewContext() { return SecurityContextHolder.createEmptyContext(); }
一般這種狀況是用戶沒有登錄,那麼一般會通過AnonymousAuthenticationFilter(
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/AnonymousAuthenticationFilter.java
),其Authentication的值就是AnonymousAuthenticationToken
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.javaless
/** * Default behaviour for successful authentication. * <ol> * <li>Sets the successful <tt>Authentication</tt> object on the * {@link SecurityContextHolder}</li> * <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li> * <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured * <tt>ApplicationEventPublisher</tt></li> * <li>Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.</li> * </ol> * * Subclasses can override this method to continue the {@link FilterChain} after * successful authentication. * @param request * @param response * @param chain * @param authResult the object returned from the <tt>attemptAuthentication</tt> * method. * @throws IOException * @throws ServletException */ protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } SecurityContextHolder.getContext().setAuthentication(authResult); rememberMeServices.loginSuccess(request, response, authResult); // Fire event if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } successHandler.onAuthenticationSuccess(request, response, authResult); }
鑑權成功以後,會將authentication寫入到SecurityContextHolder的context中。
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/SecurityContextPersistenceFilter.javaide
try { SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder .getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything // else. SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); if (debug) { logger.debug("SecurityContextHolder now cleared, as request processing completed"); } }
能夠看到SecurityContextPersistenceFilter在filter執行完以後,finally的時候,調用了saveContext來保存context的東西到session中。
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/context/HttpSessionSecurityContextRepository.javaui
/** * Stores the supplied security context in the session (if available) and if it * has changed since it was set at the start of the request. If the * AuthenticationTrustResolver identifies the current user as anonymous, then the * context will not be stored. * * @param context the context object obtained from the SecurityContextHolder after * the request has been processed by the filter chain. * SecurityContextHolder.getContext() cannot be used to obtain the context as it * has already been cleared by the time this method is called. * */ @Override protected void saveContext(SecurityContext context) { final Authentication authentication = context.getAuthentication(); HttpSession httpSession = request.getSession(false); // See SEC-776 if (authentication == null || trustResolver.isAnonymous(authentication)) { if (logger.isDebugEnabled()) { logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession."); } if (httpSession != null && authBeforeExecution != null) { // SEC-1587 A non-anonymous context may still be in the session // SEC-1735 remove if the contextBeforeExecution was not anonymous httpSession.removeAttribute(springSecurityContextKey); } return; } if (httpSession == null) { httpSession = createNewSessionIfAllowed(context); } // If HttpSession exists, store current SecurityContext but only if it has // actually changed in this thread (see SEC-37, SEC-1307, SEC-1528) if (httpSession != null) { // We may have a new session, so check also whether the context attribute // is set SEC-1561 if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) { httpSession.setAttribute(springSecurityContextKey, context); if (logger.isDebugEnabled()) { logger.debug("SecurityContext '" + context + "' stored to HttpSession: '" + httpSession); } } } }
能夠看到這裏把context設置進入session當中,這樣就關聯起來了。
能夠看到SecurityContextPersistenceFilter經過往session存取名爲SPRING_SECURITY_CONTEXT,值爲SecurityContext的attribute,來爲後續filter創建所需的上下文,包括登陸態。其中:this
所以它的filter順序排在前面就不足爲奇了。