spring security context

spring security context 谈到他我们就得谈一谈他的作用,生命周期接口如下

public interface SecurityContext extends Serializable {
    Authentication getAuthentication();

    void setAuthentication(Authentication var1);
}

说白了就是在当前请求下的各个环节如过滤器中或者controller中都能通过context获取该认证数据,我们的认证过后的Authentication 接口实例对象就放在里面,但是spring给我们封装了一个工具类HttpRequestResponseHolder,其实现原理也很简单通过ThreadLocal 将spring security的context方法里面,以保证在当前线程的可以随意获取。

总而言之其作用就是在各个地方通过以下方式获取认证信息

SecurityContextHolder.getContext().getAuthentication().getName()。。。。

SecurityContextPersistenceFilter

 

该过滤器位于拦截器的较前端,其基本功能就是

构建spring security context环境,并放入HttpRequestResponseHolder中供当前线程访问

调用拦截器链的下一个拦截器

进行HttpRequestResponseHolder中spring security context 的清理

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


		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
        //是不是一个新的request,不是则执行拦截器链的下一个
		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);
        //如果是饥饿模式的话就立即创建session
		if (forceEagerSessionCreation) {
			HttpSession session = request.getSession();

			if (debug && session.isNew()) {
				logger.debug("Eagerly created session: " + session.getId());
			}
		}
        //就相当于RequestResponse的 包装器
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
        
        //通过加载context 如果有直接返回,没有返回null
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

		try {
            //将context设置到SecurityContextHolder中去
			SecurityContextHolder.setContext(contextBeforeChainExecution);

			chain.doFilter(holder.getRequest(), holder.getResponse());

		}
		finally {
			/*
                退出是清理SecurityContextHolder的当前线程的context
			    repo进行保存指定context,以便下一次请求获取
			 */
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
			
			SecurityContextHolder.clearContext();

			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());

			request.removeAttribute(FILTER_APPLIED);

			if (debug) {
				logger.debug("SecurityContextHolder now cleared, as request processing completed");
			}
		}
	}

其中出现了一个我们目前还不熟悉的service 类,即SecurityContextRepository repo ,实现类接口如下:

public interface SecurityContextRepository {

	
	SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);


	void saveContext(SecurityContext context, HttpServletRequest request,
			HttpServletResponse response);


	boolean containsContext(HttpServletRequest request);
}

我们来看一个实现类就一目了然了:

/*
    该实现类通过session的attribute进行spring security context的存取功能
*/
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {


public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
		HttpServletRequest request = requestResponseHolder.getRequest();
		HttpServletResponse response = requestResponseHolder.getResponse();
		//获取当前session
		HttpSession httpSession = request.getSession(false);
		//读取session 中 属性名为SPRING_SECURITY_CONTEXT的属性
		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);

		requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(
				request, wrappedResponse));

		return context;
	}
}

看到这里我想你已经明白了吧,这相当于是一个 dao,只不过spring security context数据的存取是放到了session中。

总结:

通过session 获取spring security context环境,session中没有就创建一个空的,并放入HttpRequestResponseHolder中供当前线程访问

调用拦截器链的下一个拦截器

进行HttpRequestResponseHolder中spring security context 的清理,并通过session service存取session中。

 

认证与授权

 

介绍认证与授权作者将会规定一个场景即:用户匿名访问,被拦截后,进行登录,信息故意填错,在进行登录,信息正确,在访问一个没有权限的页面。

有了以上场景,我们就可以进行流程的分析了。

匿名访问经过的第一个待分析的拦截器AnonymousAuthenticationFilter

public class AnonymousAuthenticationFilter extends GenericFilterBean implements
		InitializingBean {
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
        
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
            /*
                创建一个匿名认证并放入context中

                这是Authentication的实现类
		        AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
				principal, authorities);
		
            */
			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);
	}
}

经过这个过滤器之后,当前线程的spring security context中的authentication就为AnonymousAuthenticationToken的实例了

匿名访问经过的第二个待分析的拦截器ExceptionTranslationFilter

该拦截器看名字也知道他是一个异常处理拦截器,直接调用下一个拦截器,如果下一个拦截器抛出异常在过来分析剩下的代码。

比如认证异常,授权异常等,如果出现了认证异常,会调用相关的异常处理器进行处理,具体处理在handleSpringSecurityException(request, response, chain, ase);这个函数中

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) {
          
            //省略代码了部分代码,这个是核心代码
            handleSpringSecurityException(request, response, chain, ase);
        }
}
private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
        //如果异常时认证异常
		if (exception instanceof AuthenticationException) {
			  //调用该方法进行认证异常处理
			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}
        //如果是授权异常
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            //如果是通过匿名认证器生成的 认证,还是触发认证异常处理
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				
				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				//调用授权异常处理器

				accessDeniedHandler.handle(request, response,
						(AccessDeniedException) exception);
			}
		}
	}
protected void sendStartAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain,
			AuthenticationException reason) throws ServletException, IOException {
		
		SecurityContextHolder.getContext().setAuthentication(null);
		requestCache.saveRequest(request, response);
		//委托给认证切入点成员 变量进行处理
		authenticationEntryPoint.commence(request, response, reason);
	}

这里又有两个新的成员变量值得谈一谈,那就是认证切入点,和 授权处理器,下面仅仅看一下认证切入点的代码实现,授权处理类似,当然,这也是和其他认证的整合点,通过认证切入点我们可以重定向到单点登录页面或者其他的什么等。

/*
    接口如下
*/
public interface AuthenticationEntryPoint {
	
	void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException;
}

/*
    当前默认的实现类为,该实现方法也很简单就是设置 response重定向到/login 路径下
*/
public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint,InitializingBean {
    public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {

		String redirectUrl = null;

		if (useForward) {

			if (forceHttps && "http".equals(request.getScheme())) {
				
				redirectUrl = buildHttpsRedirectUrlForRequest(request);
			}

			if (redirectUrl == null) {
				String loginForm = determineUrlToUseForThisRequest(request, response,
						authException);

				
				RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);

				dispatcher.forward(request, response);

				return;
			}
		}
		else {
			// 默认重定向的 /login路径下

			redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);

		}

		redirectStrategy.sendRedirect(request, response, redirectUrl);
	}
}

匿名访问经过的第三个待分析的拦截器FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
		Filter {
	
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}

	public void invoke(FilterInvocation fi) throws IOException, ServletException {
		    ......
            //进行权限的校验
			InterceptorStatusToken token = super.beforeInvocation(fi);
            ......
		}
	}
}

   //权限校验方法如下
protected InterceptorStatusToken beforeInvocation(Object object) {
		
	
        //元数据就是我们使用http/intercept-url 进行设置的
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
           ....
		try {
            //交给授权管理器进行权限的校验
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));
            
			throw accessDeniedException;
		}

	}

这又将会引入两个新的成员 ,accessDecisionManager 和投票器。

accessDecisionManager的默认实现类是AffirmativeBased  ,其实现机制是只要有一个投票器返回true,就有权限放行。

 

 

匿名访问流程总结

匿名用户经过  匿名访问过滤器 将会在contextHolder中存储一个匿名认证,经过异常处理拦截器,经过权限校验拦截器,由于是匿名认证所以权限管理器通过投票器校验失败,抛出权限校验异常,异常处理拦截器处理调用认证切入点重定向到/login