Spring Security默认过滤链认识

前言

最近重新学习了一下Spring Security,对整个认证流程有了一个自己的认识。但今天写的是一个前置的知识点,Spring Security启动后默认加载的一些过滤器,接着让我们来看看究竟是哪些

1 启动后过滤链总览

[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@486bc9a4,
org.springframework.security.web.context.SecurityContextPersistenceFilter@8ed9cf, 
org.springframework.security.web.header.HeaderWriterFilter@2d2acd89,
org.springframework.security.web.authentication.logout.LogoutFilter@1f193686,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3b36e000,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@377008df, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@2bc9a775,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@7965a51c, 
org.springframework.security.web.session.SessionManagementFilter@1e6dad8, 
org.springframework.security.web.access.ExceptionTranslationFilter@3fa76c61,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@9d1a267
]

2 过滤器分析

2.1 WebAsyncManagerIntegrationFilter

/**
 * 将SecurityContext和WebAsyncManager整合起来
 * Provides integration between the {@link SecurityContext} and Spring Web's
 * {@link WebAsyncManager} by using the
 * {@link SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest, Callable)}
 * to populate the {@link SecurityContext} on the {@link Callable}.
 *
 * @author Rob Winch
 * @see SecurityContextCallableProcessingInterceptor
 */
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
	private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
        //从request的attribute中得到或创建WebAsyncManager
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        //从asyncManager中得到SecurityContextCallableProcessingInterceptor,如果为空,下面创建
		SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
				.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
		if (securityProcessingInterceptor == null) {
			asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
					new SecurityContextCallableProcessingInterceptor());
		}

		filterChain.doFilter(request, response);
	}
}

SecurityContextCallableProcessingInterceptor

/**
 * 

* Allows for integration with Spring MVC's {@link Callable} support. *

*

* 当调用拦截器preProcess方法时 * 将注入的SecurityContext存储进SecurityContextHolder * 当调用拦截器postProcess方法时 * SecurityContextHolder#clearContext()清除SecurityContext * A {@link CallableProcessingInterceptor} that establishes the injected * {@link SecurityContext} on the {@link SecurityContextHolder} when * {@link #preProcess(NativeWebRequest, Callable)} is invoked. It also clear out the * {@link SecurityContextHolder} by invoking {@link SecurityContextHolder#clearContext()} * in the {@link #postProcess(NativeWebRequest, Callable, Object)} method. *

* * @author Rob Winch * @since 3.2 */
public final class SecurityContextCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter { private volatile SecurityContext securityContext; /** * 当调用beforeConcurrentHandling后,使用SecurityContextHolder的SecurityContext * Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the * {@link SecurityContext} from the {@link SecurityContextHolder} at the time * {@link #beforeConcurrentHandling(NativeWebRequest, Callable)} is invoked. */ public SecurityContextCallableProcessingInterceptor() { } /** * Creates a new {@link SecurityContextCallableProcessingInterceptor} with the * specified {@link SecurityContext}. * @param securityContext the {@link SecurityContext} to set on the * {@link SecurityContextHolder} in {@link #preProcess(NativeWebRequest, Callable)}. * Cannot be null. * @throws IllegalArgumentException if {@link SecurityContext} is null. */ public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) { Assert.notNull(securityContext, "securityContext cannot be null"); setSecurityContext(securityContext); } @Override public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) { if (securityContext == null) { setSecurityContext(SecurityContextHolder.getContext()); } } @Override public <T> void preProcess(NativeWebRequest request, Callable<T> task) { SecurityContextHolder.setContext(securityContext); } @Override public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) { SecurityContextHolder.clearContext(); } private void setSecurityContext(SecurityContext securityContext) { this.securityContext = securityContext; } }

2.2 SecurityContextPersistenceFilter

/**
 * 从配置的SecurityContextRepository中得到信息,填充SecurityContextHolder,当请求完成后又存回		
 * repository,并清空context holder
 
 * 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 HttpSession related configuration options.
 * 

* This filter will only execute once per request, to resolve servlet container * (specifically Weblogic) incompatibilities. *

* 此过滤器必须在任何认证处理机制之前,认证处理机制期望在执行的时候SecurityContextHolder中包含一个有效的 * SecurityContext * This filter MUST be executed BEFORE any authentication processing mechanisms. * Authentication processing mechanisms (e.g. BASIC, CAS processing filters etc) expect * the SecurityContextHolder to contain a valid SecurityContext * by the time they execute. *

* This is essentially a refactoring of the old * HttpSessionContextIntegrationFilter to delegate the storage issues to a * separate strategy, allowing for more customization in the way the security context is * maintained between requests. *

* forceEagerSessionCreation属性被用作确保session可用,在过滤链执行前 * The forceEagerSessionCreation property can be used to ensure that a session is * always available before the filter chain executes (the default is false, * 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() { //repo具体为HttpSessionSecurityContextRepository 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); //从repo中得到SecurityContext(如果没有则使用SecurityContextHolder.createEmptyContext()创建) 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. //清除SecurityContext,再次保存回repo 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; } }

2.3 HeaderWriterFilter

/**
 * 在响应头中写入一些选项,保护浏览器的安全
 * Filter implementation to add headers to the current response. Can be useful to add
 * certain headers which enable browser protection. Like X-Frame-Options, X-XSS-Protection
 * and X-Content-Type-Options.
 *
 * @author Marten Deinum
 * @author Josh Cummings
 * @author Ankur Pathak
 * @since 3.2
 */
public class HeaderWriterFilter extends OncePerRequestFilter {


	/**
	 * The {@link HeaderWriter} to write headers to the response.
	 * {@see CompositeHeaderWriter}
	 */
	private final List<HeaderWriter> headerWriters;

	/**
	 * 标志是否在请求开始时写headers
	 * Indicates whether to write the headers at the beginning of the request.
	 */
	private boolean shouldWriteHeadersEagerly = false;

	/**
	 * Creates a new instance.
	 *
	 * @param headerWriters the {@link HeaderWriter} instances to write out headers to the
	 * {@link HttpServletResponse}.
	 */
	public HeaderWriterFilter(List<HeaderWriter> headerWriters) {
		Assert.notEmpty(headerWriters, "headerWriters cannot be null or empty");
		this.headerWriters = headerWriters;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (this.shouldWriteHeadersEagerly) {
			doHeadersBefore(request, response, filterChain);
		} else {
			doHeadersAfter(request, response, filterChain);
		}
	}

	private void doHeadersBefore(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		writeHeaders(request, response);
		filterChain.doFilter(request, response);
	}

	private void doHeadersAfter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
				response);
		HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
				headerWriterResponse);
		try {
			filterChain.doFilter(headerWriterRequest, headerWriterResponse);
		} finally {
			headerWriterResponse.writeHeaders();
		}
	}

	void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
		for (HeaderWriter writer : this.headerWriters) {
			writer.writeHeaders(request, response);
		}
	}

	/**
	 * 允许在请求开始时就写headers
	 * Allow writing headers at the beginning of the request.
	 *
	 * @param shouldWriteHeadersEagerly boolean to allow writing headers at the beginning of the request.
	 * @author Ankur Pathak
	 * @since 5.2
	 */
	public void setShouldWriteHeadersEagerly(boolean shouldWriteHeadersEagerly) {
		this.shouldWriteHeadersEagerly = shouldWriteHeadersEagerly;
	}

	class HeaderWriterResponse extends OnCommittedResponseWrapper {
		private final HttpServletRequest request;

		HeaderWriterResponse(HttpServletRequest request, HttpServletResponse response) {
			super(response);
			this.request = request;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see org.springframework.security.web.util.OnCommittedResponseWrapper#
		 * onResponseCommitted()
		 */
		@Override
		protected void onResponseCommitted() {
			writeHeaders();
			this.disableOnResponseCommitted();
		}

		protected void writeHeaders() {
			if (isDisableOnResponseCommitted()) {
				return;
			}
			HeaderWriterFilter.this.writeHeaders(this.request, getHttpResponse());
		}

		private HttpServletResponse getHttpResponse() {
			return (HttpServletResponse) getResponse();
		}
	}

	static class HeaderWriterRequest extends HttpServletRequestWrapper {
		private final HeaderWriterResponse response;

		HeaderWriterRequest(HttpServletRequest request, HeaderWriterResponse response) {
			super(request);
			this.response = response;
		}

		@Override
		public RequestDispatcher getRequestDispatcher(String path) {
			return new HeaderWriterRequestDispatcher(super.getRequestDispatcher(path), this.response);
		}
	}

	static class HeaderWriterRequestDispatcher implements RequestDispatcher {
		private final RequestDispatcher delegate;
		private final HeaderWriterResponse response;

		HeaderWriterRequestDispatcher(RequestDispatcher delegate, HeaderWriterResponse response) {
			this.delegate = delegate;
			this.response = response;
		}

		@Override
		public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException {
			this.delegate.forward(request, response);
		}

		@Override
		public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException {
			this.response.onResponseCommitted();
			this.delegate.include(request, response);
		}
	}
}

2.4 LogoutFilter

/**
 * Logs a principal out.
 * 

* 轮询一系列的LogoutHandler,这些Handler应该按照需要的顺序指定 * Polls a series of {@link LogoutHandler}s. The handlers should be specified in the order * they are required. Generally you will want to call logout handlers * TokenBasedRememberMeServices and SecurityContextLogoutHandler * (in that order). *

* logout后,重定向将会发生,取决于LogoutSuccessHandler或者logoutSuccessUrl,看谁的构造方法被调用 * After logout, a redirect will be performed to the URL determined by either the * configured LogoutSuccessHandler or the logoutSuccessUrl, depending on * which constructor was used. * * @author Ben Alex * @author Eddú Meléndez */ public class LogoutFilter extends GenericFilterBean { // ~ Instance fields // ================================================================================================ private RequestMatcher logoutRequestMatcher; private final LogoutHandler handler; private final LogoutSuccessHandler logoutSuccessHandler; // ~ Constructors // =================================================================================================== /** * LogoutSuccessHandler决定登出后的目的地址,LogoutHandler集合去做真正的登出操作 * Constructor which takes a LogoutSuccessHandler instance to determine the * target destination after logging out. The list of LogoutHandlers are * intended to perform the actual logout functionality (such as clearing the security * context, invalidating the session, etc.). */ public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { //将多个Handler聚合起来 this.handler = new CompositeLogoutHandler(handlers); Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null"); this.logoutSuccessHandler = logoutSuccessHandler; setFilterProcessesUrl("/logout"); } public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) { this.handler = new CompositeLogoutHandler(handlers); Assert.isTrue( !StringUtils.hasLength(logoutSuccessUrl) || UrlUtils.isValidRedirectUrl(logoutSuccessUrl), () -> logoutSuccessUrl + " isn't a valid redirect URL"); SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); if (StringUtils.hasText(logoutSuccessUrl)) { urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl); } logoutSuccessHandler = urlLogoutSuccessHandler; setFilterProcessesUrl("/logout"); } // ~ Methods // ======================================================================================================== public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (requiresLogout(request, response)) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Logging out user '" + auth + "' and transferring to logout destination"); } this.handler.logout(request, response, auth); logoutSuccessHandler.onLogoutSuccess(request, response, auth); return; } chain.doFilter(request, response); } /** * 当需要替换的时候允许子类重写 * Allow subclasses to modify when a logout should take place. * * @param request the request * @param response the response * * @return true if logout should occur, false otherwise */ protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) { return logoutRequestMatcher.matches(request); } public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) { Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null"); this.logoutRequestMatcher = logoutRequestMatcher; } public void setFilterProcessesUrl(String filterProcessesUrl) { this.logoutRequestMatcher = new AntPathRequestMatcher(filterProcessesUrl); } }

2.5 UsernamePasswordAuthenticationFilter

/**
 * 处理提交的认证,首先调用AuthenticationProcessingFilter,参数是username和password
 * Processes an authentication form submission. Called
 * {@code AuthenticationProcessingFilter} prior to Spring Security 3.0.
 * 

* Login forms must present two parameters to this filter: a username and password. The * default parameter names to use are contained in the static fields * {@link #SPRING_SECURITY_FORM_USERNAME_KEY} and * {@link #SPRING_SECURITY_FORM_PASSWORD_KEY}. The parameter names can also be changed by * setting the {@code usernameParameter} and {@code passwordParameter} properties. *

* This filter by default responds to the URL {@code /login}. * * @author Ben Alex * @author Colin Sampaleanu * @author Luke Taylor * @since 3.0 */ public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // ~ Static fields/initializers // ===================================================================================== public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; // ~ Constructors // =================================================================================================== //设置父类的RequestMatcher public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } // ~ Methods // ======================================================================================================== //只能是post方法 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); //构建UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } /** * Enables subclasses to override the composition of the password, such as by * including additional values and a separator. *

* This might be used for example if a postcode/zipcode was required in addition to * the password. A delimiter such as a pipe (|) should be used to separate the * password and extended value(s). The AuthenticationDao will need to * generate the expected password in a corresponding manner. *

* * @param request so that request attributes can be retrieved * * @return the password that will be presented in the Authentication * request token to the AuthenticationManager */
@Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } /** * Enables subclasses to override the composition of the username, such as by * including additional values and a separator. * * @param request so that request attributes can be retrieved * * @return the username that will be presented in the Authentication * request token to the AuthenticationManager */ @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } /** * 提供这个方法,以便子类可以配置放入身份验证请求的详细信息属性中的内容。 * Provided so that subclasses may configure what is put into the authentication * request's details property. * * @param request that an authentication request is being created for * @param authRequest the authentication request object that should have its details * set */ protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } /** * Sets the parameter name which will be used to obtain the username from the login * request. * * @param usernameParameter the parameter name. Defaults to "username". */ public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } /** * Sets the parameter name which will be used to obtain the password from the login * request.. * * @param passwordParameter the parameter name. Defaults to "password". */ public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } /** * Defines whether only HTTP POST requests will be allowed by this filter. If set to * true, and an authentication request is received which is not a POST request, an * exception will be raised immediately and authentication will not be attempted. The * unsuccessfulAuthentication() method will be called as if handling a failed * authentication. *

* Defaults to true but may be overridden by subclasses. */ public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return usernameParameter; } public final String getPasswordParameter() { return passwordParameter; } }

2.6 RequestCacheAwareFilter

/**
 * 如果缓存的请求与当前请求匹配,则负责重新构造已保存的请求
 * Responsible for reconstituting the saved request if one is cached and it matches the
 * current request.
 * 

* 将会调用配置的RequestCache的getMatchingRequest方法,返回空,则放入原本的request,不为空则放入 * wrappedSavedRequest进入过滤器链 * It will call * {@link RequestCache#getMatchingRequest(HttpServletRequest, HttpServletResponse) * getMatchingRequest} on the configured RequestCache. If the method returns a * value (a wrapper of the saved request), it will pass this to the filter chain's * doFilter method. If null is returned by the cache, the original request is * used and the filter has no effect. * * @author Luke Taylor * @since 3.0 */ public class RequestCacheAwareFilter extends GenericFilterBean { private RequestCache requestCache; public RequestCacheAwareFilter() { this(new HttpSessionRequestCache()); } public RequestCacheAwareFilter(RequestCache requestCache) { Assert.notNull(requestCache, "requestCache cannot be null"); this.requestCache = requestCache; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest( (HttpServletRequest) request, (HttpServletResponse) response); chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest, response); } }

2.7 SecurityContextHolderAwareRequestFilter

/**
 * 这个过滤器使用包装后的request去填充ServletRequest
 * A Filter which populates the ServletRequest with a request
 * wrapper which implements the servlet API security methods.
 * 

* {@link SecurityContextHolderAwareRequestWrapper} is extended to provide the following * additional methods: *

*
    * 允许用户确定他们是否通过身份验证,如果不则到登录页面 *
  • {@link HttpServletRequest#authenticate(HttpServletResponse)} - Allows the user to * determine if they are authenticated and if not send the user to the login page. See * {@link #setAuthenticationEntryPoint(AuthenticationEntryPoint)}.
  • * 允许用户验证时使用AuthenticationManager *
  • {@link HttpServletRequest#login(String, String)} - Allows the user to authenticate * using the {@link AuthenticationManager}. See * {@link #setAuthenticationManager(AuthenticationManager)}.
  • * 允许用户使用配置的LogoutHandler登出 *
  • {@link HttpServletRequest#logout()} - Allows the user to logout using the * {@link LogoutHandler}s configured in Spring Security. See * {@link #setLogoutHandlers(List)}.
  • * 复制调用AsyncContext#start方法的线程上的SecurityContextHolder中的SecurityContext *
  • {@link AsyncContext#start(Runnable)} - Automatically copy the * {@link SecurityContext} from the {@link SecurityContextHolder} found on the Thread that * invoked {@link AsyncContext#start(Runnable)} to the Thread that processes the * {@link Runnable}.
  • *
* * * @author Orlando Garcia Carmona * @author Ben Alex * @author Luke Taylor * @author Rob Winch * @author Eddú Meléndez */
public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean { // ~ Instance fields // ================================================================================================ private String rolePrefix = "ROLE_"; private HttpServletRequestFactory requestFactory; private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationManager authenticationManager; private List<LogoutHandler> logoutHandlers; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); // ~ Methods // ======================================================================================================== public void setRolePrefix(String rolePrefix) { Assert.notNull(rolePrefix, "Role prefix must not be null"); this.rolePrefix = rolePrefix; updateFactory(); } /** *

* 与Servlet 3 APIs集成HttpServletRequest时设置AuthenticationEntryPoint * Sets the {@link AuthenticationEntryPoint} used when integrating * {@link HttpServletRequest} with Servlet 3 APIs. Specifically, it will be used when * {@link HttpServletRequest#authenticate(HttpServletResponse)} is called and the user * is not authenticated. *

*

* 如果没有配置,则保留默认的容器行为(调用父类) * If the value is null (default), then the default container behavior will be be * retained when invoking {@link HttpServletRequest#authenticate(HttpServletResponse)} * . *

* 没有认证时才调用 * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use when * invoking {@link HttpServletRequest#authenticate(HttpServletResponse)} if the user * is not authenticated. */
public void setAuthenticationEntryPoint( AuthenticationEntryPoint authenticationEntryPoint) { this.authenticationEntryPoint = authenticationEntryPoint; } /** *

* Sets the {@link AuthenticationManager} used when integrating * {@link HttpServletRequest} with Servlet 3 APIs. Specifically, it will be used when * {@link HttpServletRequest#login(String, String)} is invoked to determine if the * user is authenticated. *

*

* If the value is null (default), then the default container behavior will be * retained when invoking {@link HttpServletRequest#login(String, String)}. *

* * @param authenticationManager the {@link AuthenticationManager} to use when invoking * {@link HttpServletRequest#login(String, String)} */
public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } /** *

* Sets the {@link LogoutHandler}s used when integrating with * {@link HttpServletRequest} with Servlet 3 APIs. Specifically it will be used when * {@link HttpServletRequest#logout()} is invoked in order to log the user out. So * long as the {@link LogoutHandler}s do not commit the {@link HttpServletResponse} * (expected), then the user is in charge of handling the response. *

*

* If the value is null (default), the default container behavior will be retained * when invoking {@link HttpServletRequest#logout()}. *

* * @param logoutHandlers the {@code List<LogoutHandler>}s when invoking * {@link HttpServletRequest#logout()}. */
public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) { this.logoutHandlers = logoutHandlers; } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { chain.doFilter(this.requestFactory.create((HttpServletRequest) req, (HttpServletResponse) res), res); } @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); updateFactory(); } private void updateFactory() { String rolePrefix = this.rolePrefix; this.requestFactory = createServlet3Factory(rolePrefix); } /** * Sets the {@link AuthenticationTrustResolver} to be used. The default is * {@link AuthenticationTrustResolverImpl}. * * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be * null. */ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { Assert.notNull(trustResolver, "trustResolver cannot be null"); this.trustResolver = trustResolver; updateFactory(); } private HttpServletRequestFactory createServlet3Factory(String rolePrefix) { HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix); factory.setTrustResolver(this.trustResolver); factory.setAuthenticationEntryPoint(this.authenticationEntryPoint); factory.setAuthenticationManager(this.authenticationManager); factory.setLogoutHandlers(this.logoutHandlers); return factory; } }

2.8 AnonymousAuthenticationFilter

/**
 * 探测SecurityContextHolder中是否存在Authentication,如果需要创建填充一个
 * Detects if there is no {@code Authentication} object in the
 * {@code SecurityContextHolder}, and populates it with one if needed.
 *
 * @author Ben Alex
 * @author Luke Taylor
 */
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
		InitializingBean {

	// ~ Instance fields
	// ================================================================================================

	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
	private String key;
	private Object principal;
	private List<GrantedAuthority> authorities;

	/**
	 * 构造方法创建一个filter,principal属性"anonymousUser",一个权限角色"ROLE_ANONYMOUS"
	 * Creates a filter with a principal named "anonymousUser" and the single authority
	 * "ROLE_ANONYMOUS".
	 *
	 * @param key the key to identify tokens created by this filter
	 */
	public AnonymousAuthenticationFilter(String key) {
		this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
	}

	/**
	 *
	 * @param key key the key to identify tokens created by this filter
	 * @param principal the principal which will be used to represent anonymous users
	 * @param authorities the authority list for anonymous users
	 */
	public AnonymousAuthenticationFilter(String key, Object principal,
			List<GrantedAuthority> authorities) {
		Assert.hasLength(key, "key cannot be null or empty");
		Assert.notNull(principal, "Anonymous authentication principal must be set");
		Assert.notNull(authorities, "Anonymous authorities must be set");
		this.key = key;
		this.principal = principal;
		this.authorities = authorities;
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public void afterPropertiesSet() {
		Assert.hasLength(key, "key must have length");
		Assert.notNull(principal, "Anonymous authentication principal must be set");
		Assert.notNull(authorities, "Anonymous authorities must be set");
	}

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

        //如果不存在,就创建一个
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			SecurityContextHolder.getContext().setAuthentication(
					createAuthentication((HttpServletRequest) req));

			if (logger.isDebugEnabled()) {
				logger.debug("Populated SecurityContextHolder with anonymous token: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}
		}

		chain.doFilter(req, res);
	}

	protected Authentication createAuthentication(HttpServletRequest request) {
		AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
				principal, authorities);
		auth.setDetails(authenticationDetailsSource.buildDetails(request));

		return auth;
	}

	public void setAuthenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		Assert.notNull(authenticationDetailsSource,
				"AuthenticationDetailsSource required");
		this.authenticationDetailsSource = authenticationDetailsSource;
	}

	public Object getPrincipal() {
		return principal;
	}

	public List<GrantedAuthority> getAuthorities() {
		return authorities;
	}
}

2.9 SessionManagementFilter

/**
 * 检测用户是否从请求开始时就已通过身份验证,如果已经通过,则调用已配置的
 * SessionAuthenticationStrategy来执行任何与会话相关的活动,如激活会话固定保护机制或检查多个并发登录
 
 * Detects that a user has been authenticated since the start of the request and, if they
 * have, calls the configured {@link SessionAuthenticationStrategy} to perform any
 * session-related activity such as activating session-fixation protection mechanisms or
 * checking for multiple concurrent logins.
 *
 * @author Martin Algesten
 * @author Luke Taylor
 * @since 2.0
 */
public class SessionManagementFilter extends GenericFilterBean {
	// ~ Static fields/initializers
	// =====================================================================================

	static final String FILTER_APPLIED = "__spring_security_session_mgmt_filter_applied";

	// ~ Instance fields
	// ================================================================================================

	private final SecurityContextRepository securityContextRepository;
	private SessionAuthenticationStrategy sessionAuthenticationStrategy;
	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
	private InvalidSessionStrategy invalidSessionStrategy = null;
	private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

	public SessionManagementFilter(SecurityContextRepository securityContextRepository) {
		this(securityContextRepository, new SessionFixationProtectionStrategy());
	}

	public SessionManagementFilter(SecurityContextRepository securityContextRepository,
			SessionAuthenticationStrategy sessionStrategy) {
		Assert.notNull(securityContextRepository,
				"SecurityContextRepository cannot be null");
		Assert.notNull(sessionStrategy, "SessionAuthenticationStrategy cannot be null");
		this.securityContextRepository = securityContextRepository;
		this.sessionAuthenticationStrategy = sessionStrategy;
	}

	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) {
			chain.doFilter(request, response);
			return;
		}

		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

		if (!securityContextRepository.containsContext(request)) {
			Authentication authentication = SecurityContextHolder.getContext()
					.getAuthentication();

            //用户已经认证,并且不是匿名用户,调用session strategy
			if (authentication != null && !trustResolver.isAnonymous(authentication)) {
				// The user has been authenticated during the current request, so call the
				// session strategy
				try {
					sessionAuthenticationStrategy.onAuthentication(authentication,
							request, response);
				}
				catch (SessionAuthenticationException e) {
					// The session strategy can reject the authentication
					logger.debug(
							"SessionAuthenticationStrategy rejected the authentication object",
							e);
					SecurityContextHolder.clearContext();
					failureHandler.onAuthenticationFailure(request, response, e);

					return;
				}
                //立刻把context放入SecurityContextHolder
				// Eagerly save the security context to make it available for any possible
				// re-entrant
				// requests which may occur before the current request completes.
				// SEC-1396.
				securityContextRepository.saveContext(SecurityContextHolder.getContext(),
						request, response);
			}
			else {
				// No security context or authentication present. Check for a session
				// timeout
				if (request.getRequestedSessionId() != null
						&& !request.isRequestedSessionIdValid()) {
					if (logger.isDebugEnabled()) {
						logger.debug("Requested session ID "
								+ request.getRequestedSessionId() + " is invalid.");
					}

					if (invalidSessionStrategy != null) {
						invalidSessionStrategy
								.onInvalidSessionDetected(request, response);
						return;
					}
				}
			}
		}

		chain.doFilter(request, response);
	}

	/**
	 * 如果没有设置就什么也不做
	 * Sets the strategy which will be invoked instead of allowing the filter chain to
	 * proceed, if the user agent requests an invalid session ID. If the property is not
	 * set, no action will be taken.
	 *
	 * @param invalidSessionStrategy the strategy to invoke. Typically a
	 * {@link SimpleRedirectInvalidSessionStrategy}.
	 */
	public void setInvalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) {
		this.invalidSessionStrategy = invalidSessionStrategy;
	}

	/**
	 * 验证失败的处理器
	 * The handler which will be invoked if the AuthenticatedSessionStrategy
	 * raises a SessionAuthenticationException, indicating that the user is not
	 * allowed to be authenticated for this session (typically because they already have
	 * too many sessions open).
	 *
	 */
	public void setAuthenticationFailureHandler(
			AuthenticationFailureHandler failureHandler) {
		Assert.notNull(failureHandler, "failureHandler cannot be null");
		this.failureHandler = failureHandler;
	}

	/**
	 * 默认的是AuthenticationTrustResolverImpl
	 * Sets the {@link AuthenticationTrustResolver} to be used. The default is
	 * {@link AuthenticationTrustResolverImpl}.
	 *
	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
	 * null.
	 */
	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
		Assert.notNull(trustResolver, "trustResolver cannot be null");
		this.trustResolver = trustResolver;
	}
}

2.10 ExceptionTranslationFilter

/**
 * 处理在过滤链中抛出的AccessDeniedException和AuthenticationException
 * Handles any AccessDeniedException and AuthenticationException
 * thrown within the filter chain.
 * 

* 这个过滤器是必要的,因为它提供了Java异常和HTTP响应之间的桥梁。它只关心维护用户界面,没有在安全上做增强 * This filter is necessary because it provides the bridge between Java exceptions and * HTTP responses. It is solely concerned with maintaining the user interface. This filter * does not do any actual security enforcement. *

* 如果AuthenticationException被检测到了,会调用authenticationEntryPoint做处理 * If an {@link AuthenticationException} is detected, the filter will launch the * authenticationEntryPoint. This allows common handling of authentication * failures originating from any subclass of * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor}. *

* 如果AccessDeniedException被探测到了,会检查是否是匿名用户,如果是,就使用 * authenticationEntryPoint处理,如果不是,则会委派给AccessDeniedHandler处理 * If an {@link AccessDeniedException} is detected, the filter will determine whether or * not the user is an anonymous user. If they are an anonymous user, the * authenticationEntryPoint will be launched. If they are not an anonymous * user, the filter will delegate to the * {@link org.springframework.security.web.access.AccessDeniedHandler}. By default the * filter will use {@link org.springframework.security.web.access.AccessDeniedHandlerImpl}. *

* 使用这个filter,需要配置authenticationEntryPoint和requestCache * To use this filter, it is necessary to specify the following properties: *

    *
  • authenticationEntryPoint indicates the handler that should commence * the authentication process if an AuthenticationException is detected. Note * that this may also switch the current protocol from http to https for an SSL login.
  • *
  • requestCache determines the strategy used to save a request during the * authentication process in order that it may be retrieved and reused once the user has * authenticated. The default implementation is {@link HttpSessionRequestCache}.
  • *
* * @author Ben Alex * @author colin sampaleanu */
public class ExceptionTranslationFilter extends GenericFilterBean { // ~ Instance fields // ================================================================================================ private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private RequestCache requestCache = new HttpSessionRequestCache(); private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) { this(authenticationEntryPoint, new HttpSessionRequestCache()); } public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint, RequestCache requestCache) { Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null"); Assert.notNull(requestCache, "requestCache cannot be null"); this.authenticationEntryPoint = authenticationEntryPoint; this.requestCache = requestCache; } // ~ Methods // ======================================================================================================== @Override public void afterPropertiesSet() { Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified"); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer .getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType( AccessDeniedException.class, causeChain); } if (ase != null) { if (response.isCommitted()) { throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex); } //处理具体逻辑在这个方法中 handleSpringSecurityException(request, response, chain, ase); } else { // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); } } } public AuthenticationEntryPoint getAuthenticationEntryPoint() { return authenticationEntryPoint; } protected AuthenticationTrustResolver getAuthenticationTrustResolver() { return authenticationTrustResolver; } private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { if (exception instanceof AuthenticationException) { logger.debug( "Authentication exception occurred; redirecting to authentication entry point", exception); sendStartAuthentication(request, response, chain, (AuthenticationException) exception); } else if (exception instanceof AccessDeniedException) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) { logger.debug( "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception); sendStartAuthentication( request, response, chain, new InsufficientAuthenticationException( messages.getMessage( "ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource"))); } else { logger.debug( "Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception); accessDeniedHandler.handle(request, response, (AccessDeniedException) exception); } } } protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { // SEC-112: Clear the SecurityContextHolder's Authentication, as the // existing Authentication is no longer considered valid SecurityContextHolder.getContext().setAuthentication(null); requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); authenticationEntryPoint.commence(request, response, reason); } public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required"); this.accessDeniedHandler = accessDeniedHandler; } public void setAuthenticationTrustResolver( AuthenticationTrustResolver authenticationTrustResolver) { Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must not be null"); this.authenticationTrustResolver = authenticationTrustResolver; } public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) { Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null"); this.throwableAnalyzer = throwableAnalyzer; } /** * Default implementation of ThrowableAnalyzer which is capable of also * unwrapping ServletExceptions. */ private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer { /** * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap() */ protected void initExtractorMap() { super.initExtractorMap(); registerExtractor(ServletException.class, throwable -> { ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class); return ((ServletException) throwable).getRootCause(); }); } } }

2.11 FilterSecurityInterceptor

/**
 * 通过过滤器实现对HTTP资源执行安全处理
 * Performs security handling of HTTP resources via a filter implementation.
 * 

* The SecurityMetadataSource required by this security interceptor is of * type {@link FilterInvocationSecurityMetadataSource}. *

* Refer to {@link AbstractSecurityInterceptor} for details on the workflow. *

* * @author Ben Alex * @author Rob Winch */
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { // ~ Static fields/initializers // ===================================================================================== private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied"; // ~ Instance fields // ================================================================================================ private FilterInvocationSecurityMetadataSource securityMetadataSource; private boolean observeOncePerRequest = true; // ~ Methods // ======================================================================================================== /** * Not used (we rely on IoC container lifecycle services instead) * * @param arg0 ignored * */ public void init(FilterConfig arg0) { } /** * Not used (we rely on IoC container lifecycle services instead) */ public void destroy() { } /** * Method that is actually called by the filter chain. Simply delegates to the * {@link #invoke(FilterInvocation)} method. * * @param request the servlet request * @param response the servlet response * @param chain the filter chain * * @throws IOException if the filter chain fails * @throws ServletException if the filter chain fails */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) { this.securityMetadataSource = newSource; } public Class<?> getSecureObjectClass() { return FilterInvocation.class; } public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // 第一次调用,进行安全检查 // first time this request being called, so perform security checking if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } } /** * 指示是否将会观察到每个请求一次的处理,默认是true * Indicates whether once-per-request handling will be observed. By default this is * true, meaning the FilterSecurityInterceptor will only * execute once-per-request. Sometimes users may wish it to execute more than once per * request, such as when JSP forwards are being used and filter security is desired on * each included fragment of the HTTP request. * * @return true (the default) if once-per-request is honoured, otherwise * false if FilterSecurityInterceptor will enforce * authorizations for each and every fragment of the HTTP request. */ public boolean isObserveOncePerRequest() { return observeOncePerRequest; } public void setObserveOncePerRequest(boolean observeOncePerRequest) { this.observeOncePerRequest = observeOncePerRequest; } }

你可能感兴趣的:(Spring,Security)