搬砖民工 Security 第三天

照例先拜读大佬文章

徐靖峰

3. 过滤器详解

按照大佬博文的走向,前面几篇并没有提到过滤器这个东西。然而在看前面几篇博文的时候,就会产生疑问:
Authentication 是从哪里来的,问什么它出现在认证环节的时候,里面就已经封装好了数据。

读到当前篇章的时候这个问题得到解答。SpringSecurity使用springSecurityFilterChian作为安全过滤的入口。

回答上面的问题,答案如下:
用户的信息在经过AbstractAuthenticationProcessingFilter的时候,AbstractAuthenticationProcessingFilter#doFilter方法调用了其子类UsernamePasswordAuthenticationFilter实现的attemptAuthentication()方法来对用户的数据做了一次UsernamePasswordAuthenticaionToken封装,并设置了Details

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 authRequest 
			= new UsernamePasswordAuthenticationToken(username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		//提交封装好的信息给AuthenticationManager认证
		return this.getAuthenticationManager().authenticate(authRequest);
	}

当然除了上面的UsernamePasswordAuthenticationFilter以外还有很多的Filter在数据流转的过程起作用,以下:

Filter
作用
SecurityContextPersistenceFilter主要两个职责:
1. 请求来临时,创建SecurityContext 安全上下文信息;
2.请求结束时清空SecurityContextHodler
HeaderWriterFilter用来给http响应添加一些Header,比如X-Frame-Options,X-XSS-Protection*,X-Content-Type-Options
CsrfFilterSecurity4.x默认开启的过滤器,用于防止CSRF攻击
LogoutFilter处理注销的过滤器
UsernamePasswordAuthenticationFilter封装用户信息,提交认证等流程都在这个过滤器中实现
RequestCacheAwareFilter内部维护一个RequestCache,用于缓存request请求
SecurityContextHolderAwareRequestFilter对ServletRequest进行再一次的封装,使得Request具有更加丰富的API
AnonymousAuthenticationFilter匿名身份过滤器,用于处理不用登陆也能访问的资源,这时的用户用户拥有匿名的身份
SessionManagerFilter和Session相关的过滤器,内部存储了一个SessionAuthenticationStrategy,两者组合使用:
1.防止session-fixation protection attack ;
2. 限同一用户开启多个会话的数量
ExceptionTranslationFilter和异常处理相关的过滤器,但是一般不自己处理异常而是将一场交给内部维护的一些类去处理
FilterSecurityInterceptor这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断都是通过该类进行的。

以上黑体加粗的都是被认为是Security 的核心过滤器。

SecurityContextPersistenceFilter

在我以前的开发场景中,如果我需要保存用户信息,我会使用session来保存用户信息。Security也是通过session来保存用户信息,后续的通过sessionid来鉴别用户是否已经登陆。
Security使用了SecurityContextHolder来存储用户信息:

SecurityContextPersistenceFilter

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());
			}
		}
		//包装一下request和response
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
		//从Session中获取安全上下文信息
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
		try {
		//请求开始时设置安全上下文信息,这样就避免用户直接从Session中获取安全上下文信息
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
			//请求结束后清空安全上下文信息
			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");
			}
		}
	}
}

HttpSessionSecurityContextRepository

public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
   // 'SPRING_SECURITY_CONTEXT'是安全上下文默认存储在Session中的键值
   public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
   ...
   private final Object contextObject = SecurityContextHolder.createEmptyContext();
   private boolean allowSessionCreation = true;
   private boolean disableUrlRewriting = false;
   private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;

   private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

   //从当前request中取出安全上下文,如果session为空,则会返回一个新的安全上下文
   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) {
         context = generateNewContext();
      }
      ...
      return context;
   }

   ...

   public boolean containsContext(HttpServletRequest request) {
      HttpSession session = request.getSession(false);
      if (session == null) {
         return false;
      }
      return session.getAttribute(springSecurityContextKey) != null;
   }

   private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
      if (httpSession == null) {
         return null;
      }
      ...
      // Session存在的情况下,尝试获取其中的SecurityContext
      Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
      if (contextFromSession == null) {
         return null;
      }
      ...
      return (SecurityContext) contextFromSession;
   }

   //初次请求时创建一个新的SecurityContext实例
   protected SecurityContext generateNewContext() {
      return SecurityContextHolder.createEmptyContext();
   }

}
UsernamePasswordAuthenticationFilter

表单认证是最常用的一种认证方式,一个最直观的业务场景便是允许用户 在表单中输入用户名和密码进行登陆,而这背后的UsernamePasswordAuthenticationFilter,在整个SpringSecurity的认证体系中扮演了一个至关重要的角色。
盗来的图,真好用:|
上述时序图,主要的职责就是调取身份验证器校验用户的身份。

public Authentication attemptAuthentication(HttpServletRequest request,
      HttpServletResponse response) throws AuthenticationException {
   //获取表单中的用户名和密码
   String username = obtainUsername(request);
   String password = obtainPassword(request);
   ...
   username = username.trim();
   //组装成username+password形式的token
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
         username, password);
   // Allow subclasses to set the "details" property
   setDetails(request, authRequest);
   //交给内部的AuthenticationManager去认证,并返回认证信息
   return this.getAuthenticationManager().authenticate(authRequest);
}

很简单的一个方法,从request中去获得用户名和密码,做一下token封装,调一下认证管理器认证一下。然而事实并没有那么简单,由此我也知道了Abstract类的作用了。父类AbstractAuthenticationProcessingFilter实现了大量的细节,把这个比较不重要而又很重要的交给我们的定制。

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
      implements ApplicationEventPublisherAware, MessageSourceAware {
	//包含了一个身份认证器
	private AuthenticationManager authenticationManager;
	//用于实现remeberMe
	private RememberMeServices rememberMeServices = new NullRememberMeServices();
	private RequestMatcher requiresAuthenticationRequestMatcher;
	//这两个Handler很关键,分别代表了认证成功和失败相应的处理器
	private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
	private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
	
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		...
		Authentication authResult;
		try {
			//此处实际上就是调用UsernamePasswordAuthenticationFilter的attemptAuthentication方法
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				//子类未完成认证,立刻返回
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		//在认证过程中可以直接抛出异常,在过滤器中,就像此处一样,进行捕获
		catch (InternalAuthenticationServiceException failed) {
			//内部服务异常
			unsuccessfulAuthentication(request, response, failed);
			return;
		}
		catch (AuthenticationException failed) {
			//认证失败
			unsuccessfulAuthentication(request, response, failed);
			return;
		}
		//认证成功
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
		//注意,认证成功后过滤器把authResult结果也传递给了成功处理器
		successfulAuthentication(request, response, chain, authResult);
	}
	
}
AnonymousAuthenticationFilter

Spring Security 为了整体逻辑上的统一,即便没有认证通过的用户也赋予了一个身份。AnonymousAuthenticationFilter这个过滤器位于UsernamePasswordAuthenticationFilter后面ExceptionTranslationFilter前面,中间还有类似的用户身份认证过滤器BasicAuthticationFilter,RememberMeAuthenticationFilter,如果没有在前面的过滤器中给SecurityContext设置上用户信息,那么在这个过滤器中就会设置一个匿名的用户身份。

public class AnonymousAuthenticationFilter extends GenericFilterBean implements
      InitializingBean {

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


   //自动创建一个"anonymousUser"的匿名用户,其具有ANONYMOUS角色
   public AnonymousAuthenticationFilter(String key) {
      this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
   }

   /**
    * @param key key用来识别该过滤器创建的身份
    * @param principal principal代表匿名用户的身份
    * @param authorities authorities代表匿名用户的权限集合
    */
   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;
   }

   ...

   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
         throws IOException, ServletException {
      //过滤器链都执行到匿名认证过滤器这儿了还没有身份信息,塞一个匿名身份进去
      if (SecurityContextHolder.getContext().getAuthentication() == null) {
         SecurityContextHolder.getContext().setAuthentication(
               createAuthentication((HttpServletRequest) req));
      }
      chain.doFilter(req, res);
   }

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

      return auth;
   }
   ...
}

对比AnonymousAuthenticationFilterUsernamePasswordAuthenticationFilter就可以发现一些门道了,UsernamePasswordAuthenticationToken对应AnonymousAuthenticationToken,他们都是Authentication的实现类,而Authentication则是被SecurityContextHolder(SecurityContext)持有的,一切都被串联在了一起。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码ge寂寞

谢谢老板,老板大气,老板入大厂

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值