spring-security(二十一)核心Filter-UsernamePasswordAuthenticationFilter

一、UsernamePasswordAuthenticationFilter功能和属性
到目前为止,我们已经探讨了三个主要的filter,当采用默认配置时spring security会自动给我们追加这三个filter,现在我们的filter中还差一个处理用户认证的filter,下面我们就主要讨论下最常用的认证filter-UsernamePasswordAuthenticationFilter。

1.重要属性
  • AuthenticationManager 认证用,默认情况下是ProviderManager
  • RememberMeServices 如果启用remember功能,spring会给我们注入一个TokenBasedRememberMeServices,如果不启用remember功能,采用默认的NullRememberMeServices。
  • RequestMatcher 用来判断当前HttpServletRequest请求是否需要用当前过滤器进行处理,默认匹配URL是/login,httpMethod是POST的请求
  • SessionAuthenticationStrategy 认证成功后会调用这个类的onAuthentication,来做一些session的处理
  • AuthenticationSuccessHandler 认证成功后处理器,默认采用SavedRequestAwareAuthenticationSuccessHandler,会将用户重定向到引起认证处理的原始请求,继续之前的操作
  • AuthenticationFailureHandler 认证失败后的处理器,默认将用户重定向到defaultFailureUrl,如 /login?error

2.处理逻辑
2.1 当请求进入到这个过滤器时,首先利用requiresAuthenticationRequestMatcher属性对request进行匹配,如果匹配成功就继续执行filter里面的逻辑,如果不匹配直接执行下一个filter。
2.2 如果2.1匹配成功开始尝试认证处理
2.2.1 首先从request中获取到username和password(参数名称可定制化),组装成一个UsernamePasswordAuthenticationToken
2.2.2 如果有额外信息需要保存调用authenticationDetailsSource.buildDetails构建详情追加的Token中
2.2.3 之后调用authenticationManager.authenticate方法进行认证,认证成功后这个方法会给我们返回一个组装好的Authentication对象
2.2.3.1认证成功后执行逻辑
    a. 调用sessionStrategy.onAuthentication方法,对session进行一些处理,这个功能比较重要,下面会再详细讨论
    b. 将组装好的Authentication存放到SecurityContextHolder中
    c. 调用rememberMeServices的loginSuccess方法,如果配置了remember功能这边会将我们的用户名和密码序列化后存入到cookie中,对应的key是remember-me
    d.接着调用successHandler的onAuthenticationSuccess方法,默认继续执行引起认证处理的原始请求
2.2.3.2 认证失败后的执行逻辑
   a.清除SecurityContextHolder中的认证信息
   b.执行rememberMeServices.loginFail方法将cookie中key是remember-me的值设置成null
3.关于SessionAuthenticationStrategy
3.1 默认配置下,spring security在servelet3.1+环境下会为我们构建ChangeSessionIdAuthenticationStrategy(低版本的servlet下构建SessionFixationProtectionStrategy)来处理session fixation攻击,如果启用了csrf,spring会再给我们启用一个CsrfAuthenticationStrategy,如果启用了并发session控制,spring会再给我注入ConcurrentSessionControlAuthenticationStrategy和RegisterSessionAuthenticationStrategy,所以在启用csrf和currency session功能后最终会有四个SessionAuthenticationStrategy,之后spring会将这四个SessionAuthenticationStrategy组合成一个CompositeSessionAuthenticationStrategy赋值到这个认证filter中。
最终的结果按顺序排列如下:
  • ConcurrentSessionControlAuthenticationStrategy 当发现同一个用户达到最大并发登陆数时,将这个用户最后活动时间最早的session设置成过期,用户对应的session信息从SessionRegistry中获取。
  • ChangeSessionIdAuthenticationStrategy 用户认证成功后修改sessionId,防止session fixation攻击
  • RegisterSessionAuthenticationStrategy 用户认证成功后将用户对应的session信息存放到SessionRegistry中,供ConcurrentSessionControlAuthenticationStrategy使用
  • CsrfAuthenticationStrategy,用户认证成功后清除原来的csrf token,再重新生成一个csrf token,存入CsrfTokenRepository中并设置到request中,供CsrfFilter使用,后续处理的csrf Token都有CsrfFilter来管理


其中ConcurrentSessionControlAuthenticationStrategy和RegisterSessionAuthenticationStrategy 对应的SessionRegistry是同一个实例,默认的实现是SessionRegistryImpl。这个类里面主要是两个hashmap,一个是用户与sessionId的对应关系,一个存储session与SessionInformation的对应关系
/** <principal:Object,SessionIdSet> */
private final ConcurrentMap<Object, Set<String>> principals = new ConcurrentHashMap<Object, Set<String>>();
/** <sessionId:Object,SessionInformation> */
private final Map<String, SessionInformation> sessionIds = new ConcurrentHashMap<String, SessionInformation>();

RegisterSessionAuthenticationStrategy会调用这个类的registerNewSession方法,向这个用户对应的sessionIds中追加一个当前sessionId和SessionInformation的对应记录。
ConcurrentSessionControlAuthenticationStrategy在认证成功后获取当前用户对应的所有sessionId,从而判断size是否达到最大限制进行并发登录控制。
二、在spring boot环境下,采用Java config机制这个filter是如何追加到servlet中对我们的请求进行拦截的呢。
在前面的章节 (spring-security(十六)Filter配置原理)中,我们知道spring 安全相关的Filter是在WebSecurity的build方法中调用HttpSecurity的build来将追加到HttpSecurity中filter列表排好序后构建成SecurityFilterChain,再把所有的SecurityFilterChain追加到FilterChainProxy中,最后通过DelegatingFilterProxy注册到ServletContext中的,下面我们主要来看下这个类是如何追加到HttpSecuriy的filter列表中的。
1.从我们的配置入口WebSecurityConfigurerAdapter类开始,采用默认配置时,在这个类的configure(HttpSecurity http)方法中(我们大多数的定制化处理都是重写这个方法来实现的),会调用http.formLogin()方法
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
    return getOrApply(new FormLoginConfigurer<HttpSecurity>());
}

在这个方法中创建了一个实现了SecurityConfigurer接口的配置类FormLoginConfigurer,通过调用getOrApply方法最终追加到HttpSecurity的configurers属性中,通过这个配置类我们也可以设置自定义的登录页面、设置认证处理的请求路径、设置登录成功后的跳转地址(不设置的话就是跳转到引起认证请求的地址)、设置登录失败后的默认跳转地址等。
2.FormLoginConfigurer类的构造函数
public FormLoginConfigurer() {
    super(new UsernamePasswordAuthenticationFilter(), null);
    usernameParameter("username");
    passwordParameter("password");
}

在构造函数中为我们创建了一个UsernamePasswordAuthenticationFilter,并设置默认的用户名和密码对应的参数名称是username、password。
3. 看下这个filter对应的属性是如何设置的
WebSecurity在构建HttpSecurity时,会调用HttpSecurity的build方法,这个方法会先执行HttpSecurity的configure()方法,就是依次调用configurers属性中各个SecurityConfigurer的configure方法
private void configure() throws Exception {
  Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
  for (SecurityConfigurer<O, B> configurer : configurers) {
     configurer.configure((B) this);
  }
}

下面来看下FormLoginConfigurer的configure方法,具体的逻辑在其父类AbstractAuthenticationFilterConfigurer中
@Override
public void configure(B http) throws Exception {
	PortMapper portMapper = http.getSharedObject(PortMapper.class);
	if (portMapper != null) {
		authenticationEntryPoint.setPortMapper(portMapper);
	}
	authFilter.setAuthenticationManager(http
			.getSharedObject(AuthenticationManager.class));
	authFilter.setAuthenticationSuccessHandler(successHandler);
	authFilter.setAuthenticationFailureHandler(failureHandler);
	if (authenticationDetailsSource != null) {
	authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
	}
	SessionAuthenticationStrategy sessionAuthenticationStrategy = http
			.getSharedObject(SessionAuthenticationStrategy.class);
	if (sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
	}
	RememberMeServices rememberMeServices = http
			.getSharedObject(RememberMeServices.class);
	if (rememberMeServices != null) {
		authFilter.setRememberMeServices(rememberMeServices);
	}
	F filter = postProcess(authFilter);
	http.addFilter(filter);
}

很明显在这个configure方法中,为filter设置了AuthenticationManager、AuthenticationSuccessHandler、AuthenticationFailureHandler、SessionAuthenticationStrategy、RememberMeServices等我们在前面看到的所需的属性。除了SessionAuthenticationStrategy,其余属性的设置都比较简单,SessionAuthenticationStrategy的设置逻辑需参考SessionManagementConfigurer的init和configure方法以及CsrfConfigurer的configure方法,这里不再具体展开。
在上面方法的最后将我们配置好的filter通过http.addFilter追加到了HttpSecurity的filter列表中,这样就可以和其他的过滤器一样对我们的请求进行过滤了。

以上就是UsernamePasswordAuthenticationFilter的主要内容,这个filter也是我们最常用的用户名和密码认证方式的主要处理类,后面我们还会陆续讲解其他的认证方式以及对应的filter。

猜你喜欢

转载自fengyilin.iteye.com/blog/2412078