文章目录
1. AuthenticationEntryPoint
2. UsernamePasswordAuthenticationFilter类
AbstractAuthenticationProcessingFilter
在整个身份验证的流程中主要处理的工作就是所有与Web资源相关的事情,并且将其封装成Authentication对象
,最后调用AuthenticationManager的验证方法
。所以UsernamePasswordAuthenticationFilter
的工作大致也是如此,只不过在这个场景下更加明确了Authentication对象的封装数据的来源和形式——使用用户名和密码。
UsernamePasswordAuthenticationFilter
继承扩展了AbstractAuthenticationProcessingFilter
,相对与AbstractAuthenticationProcessingFilter而言主要有以下几个改动:
- 属性中增加了username和password字段;
- 强制的只对POST请求应用;
- 重写了attemptAuthentication身份验证入口方法。
UsernamePasswordAuthenticationToken 封装用户名密码的基石,
在UsernamePasswordAuthenticationFilter的属性声明中额外增加了username和password的动机很容易明白,即需要从HttpRequest中获取对应的参数字段
,并将其封装进Authentication中传递给AuthenticationManager进行身份验证
。
这里让我们回顾下Authentication到底是什么?Authentication是一个接口声明,一个特定行为的声明,它并不是一个类,没有办法实例化为对象进行传递。所以我们首先需要对Authentication进行实现,使其可以被实例化。
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
在UsernamePasswordAuthenticationFilter的身份验证设计里,我们需要验证协议用简单的语言可以描述为:给我一组用户名和密码,如果匹配,那么就算验证成功。用户名即是一个唯一可以标识不同用户的字段,而密码则是检验当前的身份验证是否正确的凭证信息。在Spring Security中将使用username和password封装成Authentication的实现声明为了UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken继承了 AbstractAuthenticationToken
其主要与AbstractAuthenticationToken的区分就是针对使用用户名和密码验证的请求按照约定进行了一定的封装:将username
赋值到了principal
,而将password
赋值到了credentials
。
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
.....
}
通过UsernamePasswordAuthenticationToken实例化了Authentication接口
,继而按照流程,将其传递给AuthenticationMananger调用身份验证核心
完成相关工作。
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
将来自HTTP请求中的参数按照预先约定放入赋值给Authentication指定属性
,便是UsernamePasswordAuthenticationFilter部分最主要的改动。
3. 验证核心的工作者(AuthenticationProvider)
Web层的工作已经完成了,Authentication
接口的实现类UsernamePasswordAuthenticationToken通过AuthenticationMananger提供的验证方法作为参数被传递到了身份验证的核心组件中。我们曾多次强调过一个设计概念:AuthenticationManager接口设计上并不是用于完成特定的身份验证工作的,而是调用其所配发的AuthenticationProvider接口去实现的。
那么这里就有一个疑问,针对接口声明参数声明的Authentication,针对不同验证协议的AuthenticationProvider
的实现类们是完成对应的工作的,并且AuthenticationManager
是如何知道应该使用哪一个AuthenticationProvider才能完成对应协议的验证工作?
验证核心的大明星AuthenticationProvider接口的声明:
public interface AuthenticationProvider {
//核心验证方法
Authentication authenticate(Authentication var1) throws AuthenticationException;
//辨别否是完成相应验证工作
boolean supports(Class<?> var1);
}
AuthenticationProvider只包含两个方法声明,核心验证方法入口authenticate
方法;
另外一个便是让AuthenticationManager可以通过调用该方法辨别当前AuthenticationProvider是否是完成相应验证工作的supports
方法
在Spring Security中唯一AuthenticationManager的实现类:ProviderManager
在处理authenticate身份验证入口方法的时
- 有哪写AuthenticationProvider能验证当前传入的Authentication?为此ProviderManager便会对其所有的AuthenticationProvider做supports方法检测,直到有AuthenticationProvider能在supports方法被调用后返回true。
- 能处理当前的身份验证信息请求
- 进行验证工作。
3.1 supports验证
回到我们的场景上来:UsernamePasswordAuthenticationFilter已经封装好了一个UsernamePasswordAuthenticationToken传递给了ProviderMananger。紧接着当前ProviderMananger正焦头烂额的询问哪个AuthenticationProvider能支持这个Authentication的实现类。此时ProviderMananger所处的情况大概就跟下图一般困惑:
在ProviderMananger的视角里,所有的Authentication实现类都不具名,它不仅
不能通过自身完成验证
工作也不能独立完成判断是否支持
的工作,而是统统交给AuthenticationProvider
去完成。而不同的AuthenticationProvider开发初衷本就是为了支持指定的某种验证协议。
所以在特定的AuthenticationProvider的视角
中,他只关心当前Authentication是不是他预先设计处理的类型
即可。
在使用用户名和密码的验证场景中,验证使用的用户名和密码被封装成了UsernamePasswordAuthenticationToken对象。Spring Security便为了向UsernamePasswordAuthenticationToken对象在核心层提供相关的验证服务便继承AuthenticationProvider开发了使用用户名和密码与UserDetailsService交互并且验证密码的 DaoAuthenticationProvider
其是AbstractUserDetailsAuthenticationProvider
实现类,大部分逻辑都是通过AbstractUserDetailsAuthenticationProvider完成的 :
public boolean supports(Class<?> authentication) {
//判断当前的authentication是否是UsernamePasswordAuthenticationToken它本身(或者子孙类)
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));// isAssignableFrom 判断两个类之间是否存在继承关系使用的判断方法
}
3.1 Authentication authenticate(Authentication authentication)
完成了是否支持的supports验证后,ProviderMananger便会全权将验证工作交由DaoAuthenticationProvider进行处理了。与ProviderMananger最不同一点是,在DaoAuthenticationProvider的视角里,当前的Authentication最起码一定是UsernamePasswordAuthenticationToken的形式了,不用和ProviderMananger一样因为匮乏信息而不知道干什么。
在DaoAuthenticationProvider分别会按照预先设计一样分别从principal和credentials获取用户名和密码进行验证。
接着便是按照我们熟悉的预先设计流程,通过UserDetailsService使用username获取对应的UserDetails,最后通过对比密码是否一致,向PrivoderManager返回最终的身份验证结果与身份信息。这样一个特定场景使用用户名和密码的验证流程就完成了。
4. 小结
- UsernamePassword
AuthenticationFilter
扩展AbstractAuthenticationProcessingFilter,因为需要从HTTP请求中从指定名称的参数获取用户名和密码,并且传递给验证核心; - UsernamePassword
AuthenticationToken
扩展Authentication,因为我们设计了一套约定将用户名和密码放入了指定的属性中以便核心读取使用;
DaoAuthenticationProvider
扩展AuthenticationProvider
,因为我们需要在核心中对UsernamePasswordAuthenticationToken进行处理,并按照约定读出用户名和密码使其可以进行身份验证操作。
链接:https://www.jianshu.com/p/e98cdf23b991