AuthenticationManager 的 authentication 过程
1. 结论// 调用链AuthenticationManager.authenticate()--> ProviderManager.authenticate() --> DaoAuthenticationProvider(AbstractUserDetailsAuthenticationProvider).authenticate()// 处理在最后的 authenticate()
1. 结论
// 调用链
AuthenticationManager.authenticate() --> ProviderManager.authenticate() --> DaoAuthenticationProvider(AbstractUserDetailsAuthenticationProvider).authenticate()
// 处理
在最后的 authenticate() 方法中,调用了 UserDetailsService.loadUserByUsername() 并进行了密码校验,校验成功就构造一个认证过的 UsernamePasswordAuthenticationToken 对象放入 SecurityContext.
2. AuthenticationManager.authenticate()
public interface AuthenticationManager {
// 就这一个方法
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
ProviderManager
是它的是实现类,实际上调用的它的 authentication()
方法
3. ProviderManager.authentication()
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
// authenticate 方法
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// getProviders() -> debug发现其实就一个 DaoAuthenticationProvider.class
for (AuthenticationProvider provider : getProviders()) {
// toTest == UsernamePasswordAuthenticationToken.class
// 其实就是判断 toTest 是不是UsernamePasswordAuthenticationToken类,成立
if (!provider.supports(toTest)) {
continue;
}
try {
=========================================================================================
// 最终 DaoAuthenticationProvider.authenticate()
result = provider.authenticate(authentication);
=========================================================================================
}
4. DaoAuthenticationProvider.authenticate()
DaoAuthenticationProvider
的父类是 AbstractUserDetailsAuthenticationProvider
,实际上调用的是父类的 authentication()
,因为继承关系,所以有时候不好判断到底是父类的方法还是子类重写后的方法,建议 debug,非常清楚。
这里主要有三个步骤:
- 子类的
retrieveUser()
,里面调用了UserDetailsService.loadUserByUsername()
进行身份查找 - 子类的
additionalAuthenticationChecks()
,里面调用passwordEncoder.matches()
进行密码匹配 - 子类的
createSuccessAuthentication()
,认证完成,往SecurityContext
中放一个认证过的 auth 对象
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
// authentication
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// ....
========================================================================================
try {
// 1. 这个 retrieveUser ,就是判断用户身份,查询内存或者数据库拿到用户对象
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
// 2. 密码校验
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
Object principalToReturn = user;
// 3. 返回一个认证过的对象,注意这里 principle 传的是保存了用户信息的 user 对象,不是 String
return createSuccessAuthentication(principalToReturn, authentication, user);
}
5. DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// 1. retrieveUser(),用户身份判断
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication) {
try {
=========================================================================================
// 就是在这里调用了 UserDetailsService
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
=========================================================================================
}
// 2. additionalAuthenticationChecks() 密码校验
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
// 3. 创建一个已认证的对象
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
// 这里又回到 父类了,无语
return super.createSuccessAuthentication(principal, authentication, user);
}
// 直接粘贴在这里 super.createSuccessAuthentication()
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// 调用 UsernamePasswordAuthenticationToken 类 三个构造参数的构造器(认证过的对象创建)
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
6. UsernamePasswordUnthenticationToken构造器
为什么说选择三个参数的构造器构造的就是一个已认证的对象。我们来看看
/**
* 两个参数,构造待认证对象
* principle 传 username
* credentials 传 passord
*/
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
// 设置标识,未认证
setAuthenticated(false);
}
/**
* This constructor should only be used by <code>AuthenticationManager</code> or
* <code>AuthenticationProvider</code> implementations that are satisfied with
* producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
*
* 两个参数,构造待认证对象
* principle 传 保存了用户信息的对象(实现了UserDetails接口的对象)
* credentials 传 passord,也可以传null,因为认证过后,我们不需要知道密码,也是一种保护
* authorities 传 用户的权限列表
*/
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
// 标记已认证
super.setAuthenticated(true);
}
从这里也可以看出为什么 pinciple
和 credentials
的类型都是 Object
,因为 pinciple
认证前需要传 String
类型的 username
,认证后需要传实现了UserDetails
接口的用户对象;而 credentials
认证前需要传前端传来的String
类型的password
,认证后需要传 String
类型的密码 或者 null
。
通常都是,接收 前端传来的 username
和 password
,调用 两个参数的构造器,创建一个未认证的对象,交给AuthenticationManager
进行认证。认证成功后,调用三个参数的构造器,创建一个已认证的对象。
更多推荐
所有评论(0)