Spring Security小教程 Vol 4. 使用用户名和密码验证身份-UsernamePasswordAuthenticationFilter

前言

上一期咱们分享了Spring Security是如何经过AbstractAuthenticationProcessingFilter向Web应用向基于HTTP、浏览器的请求提供身份验证服务的。 这一次咱们针对最经常使用,也是Spring Security默认在HTTP上使用的验证过滤器UsernamePasswordAuthenticationFilter即基于用户名和密码的身份验证过滤器是如何与核心进行交互进行展开说明。目的是但愿让你们对如何在Spring Security的核心上完成一个指定的身份验证协议的扩展工做,已经涉及相关主要组件及其角色职责有个初步的了解。 这一期的内容若是有了前几期对身份验证核心的背景,相对来讲比较的简单,由于整个流程就是在原有的基础上更加具体化了场景:身份验证的数据来源是用户提交的请求,验证的凭证是用户名和密码。因为这样的缘由,这一期更像是对前几期的一个综合性的应用总结。java

第四期 UsernamePasswordAuthenticationFilter

本期的任务清单

  1. Spring Security向Web应用提供支持的相关组件
  2. 了解UsernamePasswordAuthenticationFilter的职责和实现
  3. 了解UsernamePasswordAuthenticationToken封装的身份验证信息

一. UsernamePasswordAuthenticationFilter

1.1 角色与职责

UsernamePasswordAuthenticationFilterAbstractAuthenticationProcessingFilter针对使用用户名和密码进行身份验证而定制化的一个过滤器。 在一开始咱们先经过下面的配图来回忆一下咱们的老朋友AbstractAuthenticationProcessingFilter的在框架中的角色与职责。 浏览器

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter在整个身份验证的流程中主要处理的工做就是全部与Web资源相关的事情,而且将其封装成Authentication对象,最后调用AuthenticationManager的验证方法。以UsernamePasswordAuthenticationFilter的工做大体也是如此,只不过在这个场景下更加明确了Authentication对象的封装数据的来源和形式:使用用户名和密码。bash

1.2 属性与方法

接着咱们再UsernamePasswordAuthenticationFilter的属性和方法作一个快速的了解。 UsernamePasswordAuthenticationFilter继承扩展了AbstractAuthenticationProcessingFilter,相对与AbstractAuthenticationProcessingFilter而言主要有如下几个改动:框架

UsernamePasswordAuthenticationFilter的属性中额外增长了username和password字段

  1. 属性中增长了username和password字段;
  2. 强制的只对POST请求应用;
  3. 重写了attemptAuthentication身份验证入口方法。

关于增长username和password的动机十分容易明白,在从请求中获取表单提交的用户名和密码字段便会赋值到这两个属性中。 而重写attemptAuthentication的方法主要是完了构建一个UsernamePasswordAuthenticationToken对象而且将其传递给AuthenticationManager进行身份验证。ide

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
		username, password);

// Allow subclasses to set the "details" property
setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);
复制代码

二. UsernamePasswordAuthenticationToken

2.1. 封装验证请求数据的载体

UsernamePasswordAuthenticationToken是整个身份验证流程封装了身份验证请求数据中的数据对象。 在UsernamePasswordAuthenticationFilter的属性声明中额外增长了username和password的动机很容易明白,即须要从HttpRequest中获取对应的参数字段,并将其封装进Authentication中传递给AuthenticationManager进行身份验证这里让咱们回顾下Authentication究竟是什么?Authentication是一个接口声明,一个特定行为的声明,它并非一个类,没有办法实例化为对象进行传递。因此咱们首先须要对Authentication进行实现,使其能够被实例化。 this

Authentication接口声明

UsernamePasswordAuthenticationFilter的身份验证设计里,咱们须要验证协议用简单的语言能够描述为:给我一组用户名和密码,若是匹配,那么就算验证成功。用户名便是一个惟一能够标识不一样用户的字段,而密码则是检验当前的身份验证是否正确的凭证信息。在Spring Security中便将使用username和password封装成Authentication的实现声明为了UsernamePasswordAuthenticationTokenspa

2.2. 封装用户名和密码

UsernamePasswordAuthenticationToken继承了AbstractAuthenticationToken抽象类,其主要与AbstractAuthenticationToken的区分就是针对使用用户名和密码验证的请求按照约定进行了必定的封装:将username赋值到了principal ,而将password赋值到了credentials。设计

public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
	super(authorities);
	this.principal = principal;
	this.credentials = credentials;
	super.setAuthenticated(true); // must use super, as we override
}
复制代码

经过UsernamePasswordAuthenticationToken实例化了Authentication接口,继而按照流程,将其传递给AuthenticationManager调用身份验证核心完成相关工做。3d

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
		username, password);

// Allow subclasses to set the "details" property
setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);
复制代码

以上未来自HTTP请求中的参数按照预先约定放入赋值给Authentication指定属性,即是UsernamePasswordAuthenticationFilter部分最主要的改动。code

三. 验证核心的工做者:AuthenticationProvider

Web层的工做已经完成了,Authentication接口的实现类UsernamePasswordAuthenticationToken经过AuthenticationManager提供的验证方法做为参数被传递到了身份验证的核心组件中。 咱们曾屡次强调过一个设计概念:AuthenticationManager接口设计上并非用于完成特定的身份验证工做的,而是调用其所配发的AuthenticationProvider接口去实现的。 那么这里就有一个疑问,针对接口声明参数声明的Authentication,针对不一样验证协议的AuthenticationProvider的实现类们是完成对应的工做的,而且AuthenticationManager是如何知道应该使用哪个AuthenticationProvider才能完成对应协议的验证工做? 那么咱们首先先复习下验证核心的大明星AuthenticationProvider接口的声明:

AuthenticationProvider接口声明
AuthenticationProvider只包含两个方法声明:

一个是用于验证身份请求AuthenticationToken的authenticate方法:

Authentication authenticate(Authentication authentication) throws AuthenticationException;
复制代码

另一个即是让AuthenticationManager能够经过调用该方法辨别当前AuthenticationProvider是不是完成相应验证工做的supports方法:

boolean supports(Class<?> authentication);
复制代码

对于AuthenticationProvider整个体系能说的很是多,本期只对咱们“须要了解”的AuthenticationProvider中两个接口声明的方法作个最简单的说明。其余部分在之后单独对AuthenticationProvider体系介绍的时候再进一步展开。

3.1 第一个方法- supports

在Spring Security中惟一AuthenticationManager的实现类ProviderManager,在处理authenticate身份验证入口方法的时,首先第一解决的问题即是:我手下哪一个AuthenticationProvider能验证当前传入的Authentication?为此ProviderManager便会对其全部的AuthenticationProvider作supports方法检测,直到有AuthenticationProvider能在supports方法被调用后返回true。

咱们了解了框架上的设计逻辑:先要知道知道谁能处理当前的身份验证信息请求再要求它进行验证工做。 回到咱们的场景上来:UsernamePasswordAuthenticationFilter已经封装好了一个UsernamePasswordAuthenticationToken,并将它传递给了ProviderMananger。随后ProviderMananger便会一次轮训它管理的全部AuthenticationProvider,询问是否有谁能支持这个Authentication的实现类。此时ProviderMananger所处的状况大概就跟下图通常困惑:

ProviderMananger的炼狱生活

在ProviderMananger的视角里,全部的Authentication实现类都不具名,它不只不能经过自身完成验证工做也不能独立完成判断是否支持的工做,而是通通交给AuthenticationProvider去完成。而不一样的AuthenticationProvider开发初衷本就是为了支持指定的某种验证协议,因此在特定的AuthenticationProvider的视角中,他只关心当前Authentication是否是他预先设计处理的类型便可。 在使用用户名和密码的验证场景中,验证使用的用户名和密码被封装成了UsernamePasswordAuthenticationToken对象。Spring Security便为了向UsernamePasswordAuthenticationToken对象在核心层提供相关的验证服务便继承AuthenticationProvider开发了使用用户名和密码与UserDetailsService交互而且验证密码的DaoAuthenticationProviderDaoAuthenticationProvider是AbstractUserDetailsAuthenticationProvider的实现类,DaoAuthenticationProvider针对UsernamePasswordAuthenticationToken的大部分逻辑都是经过AbstractUserDetailsAuthenticationProvider完成的。好比针对ProviderManager询问是否支持当前Authentication的supports方法:

public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}

复制代码

可能有些同窗对isAssignableFrom方法比较陌生,这是一个判断两个类之间是否存在继承关系使用的判断方法,DaoAuthenticationProvider会判断当前的Authentication的实现类是不是UsernamePasswordAuthenticationToken它自己,或者是扩展了UsernamePasswordAuthenticationToken的子孙类。返回true的场景只有一种,即是当前的Authentication是UsernamePasswordAuthenticationToken实现,换言之即是DaoAuthenticationProvider设计上须要进行处理的某种特定的验证协议的信息载体的实现。

3.2 第二个方法- authenticate

完成了是否支持的supports验证后,ProviderMananger便会全权将验证工做交由DaoAuthenticationProvider进行处理了。与ProviderMananger最不一样一点是,在DaoAuthenticationProvider的视角里,当前的Authentication最起码必定是UsernamePasswordAuthenticationToken的形式了,不用和ProviderMananger同样由于匮乏信息而不知道干什么。 在DaoAuthenticationProvider分别会按照预先设计同样分别从principal和credentials获取用户名和密码进行验证。

String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

String presentedPassword = authentication.getCredentials().toString();
复制代码

接着即是按照咱们熟悉的预先设计流程,经过UserDetailsService使用username获取对应的UserDetails,最后经过对比密码是否一致,向PrivoderManager返回最终的身份验证结果与身份信息。这样一个特定场景使用用户名和密码的验证流程就完成了。

小结

咱们先来总结下,当前出现过的针对用户名和密码扩展过的类与其为什么被扩展的缘由。

  1. UsernamePasswordAuthenticationFilter扩展AbstractAuthenticationProcessingFilter,由于须要从HTTP请求中从指定名称的参数获取用户名和密码,而且传递给验证核心;
  2. UsernamePasswordAuthenticationToken扩展Authentication,由于咱们设计了一套约定将用户名和密码放入了指定的属性中以便核心读取使用;
  3. DaoAuthenticationProvider 扩展AuthenticationProvider,由于咱们须要在核心中对UsernamePasswordAuthenticationToken进行处理,并按照约定读出用户名和密码使其能够进行身份验证操做。

客制化验证协议过程当中涉及扩展的类

结尾

本章的重点是介绍特定场景下框架是如何经过扩展指定组件来完成预设验证逻辑的交互过程。其实整个验证工做核心部分是在DaoAuthenticationProvider中进行完成的,可是这部份内容涉及到具体的验证协议的实现逻辑很是复杂,本期就暂时略过,在一下期中咱们将对验证核心最重要的组件AuthenticationProvider其依赖的组件和对应职责作一个全面的讲解。 咱们下期再见。

相关文章
相关标签/搜索