重点标识
认证流程,AuthenticationManager 统筹全局
认证逻辑就是,AuthenticationManager 管理多个AuthenticationProvider,不同的认证方式,采用不同的AuthenticationProvider去进行认证,认证通过则直接结束,如果当前AuthenticationManager 下所有的AuthenticatioProvider都认证不了,则看看AuthenticationManager有没有parent,存在parent,则调用parent ,AuthenticationManager 接着认证,不存在,则直接返回失败。
账号密码登录认证逻辑
我们知道,在加入Security后,项目启动的时候有一个过滤器叫UserNamePasswordAuthenticationFilter,这个就是账户密码登录的过滤器,我们进入看一下:
大概解释一下这部分源码,首先,看他的登录请求是不是一个POST请求,如果不是,直接返回。
接着,就是获取它的用户名,密码,是不是空的,这部分的获取方式,就是我上一篇写过的,HttpServletRequest获取,有兴趣的同学可以翻一下,这里我随便点进去一个。
request.getParameter(this.usernameParameter);
请求是POST并且账户,密码都不为空的情况下,它会再调用UsernamePasswordAuthenticationToken.unauthenticated(username, password);
顾名思义,就是未认证,这里可以理解为,标识一下,这个请求还没有进行认证。
完了。就通过this.getAuthenticationManager().authenticate(authRequest);开始认证了。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username.trim() : "";
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
好了,我们接着往下看,进入到authenticate这个方法,这个方法就是AuthenticationManager提供的,我们看看它的实现类。
很明显,他就是ProviderManager了,进去看看:
这个认证方法也大概解释一下,首先,获取到provider进行迭代遍历。
Iterator var9 = this.getProviders().iterator();
紧接着,就是通过 result = provider.authenticate(authentication);来判断,认证能不能通过。
如果通过了,则直接break,跳出去,结束,通不过,再看看下面这个方法
parentResult = this.parent.authenticate(authentication);
这里的重点是parent,就是去他的父类找下一个provider,然后递归调用。
点到parent里面看一下,我们可以看到,它还是AuthenticationManager,那由此我们就可以得到一个结论,一个AuthenticationManager是管理多个provider的,也就是说,认证的方式有多种,只要找到一种适合的,就可以通过了,这也是Spring Security所提供的好处之一,多种认证方式,如常用的用户名,密码登录,也有令牌登录之类的。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
Iterator var9 = this.getProviders().iterator();
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
if (provider.supports(toTest)) {
if (logger.isTraceEnabled()) {
Log var10000 = logger;
String var10002 = provider.getClass().getSimpleName();
++currentPosition;
var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
if (result == null && this.parent != null) {
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
} catch (ProviderNotFoundException var12) {
} catch (AuthenticationException var13) {
parentException = var13;
lastException = var13;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
验证一下,上面所说的,我们新建一个项目,引入Web依赖和Security依赖即可。
简单配置一下:只需要注入一个SecurityFilterChain,就是Security的过滤器链,然后将AuthenticationManager认证,以及需要认证的用户UserDetailsService 放进去,就可以了。
@Configuration
public class SecurityConfig {
UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());
return inMemoryUserDetailsManager;
}
AuthenticationManager authenticationManager(){
//做数据校验
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);
return providerManager;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.authenticationManager(authenticationManager()).formLogin(f->f.permitAll()).csrf(c->c.disable());
return http.build();
}
}
启动项目,进行访问,OK的。
这里还有一个点,就是我们在上面说的provider的parent,在这里,我们也可以模拟一下:
@Configuration
public class SecurityConfig {
UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());
return inMemoryUserDetailsManager;
}
AuthenticationManager authenticationManager(){
//做数据校验
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
ProviderManager providerManager = new ProviderManager(Arrays.asList(daoAuthenticationProvider),parentAuthenticationManager());
return providerManager;
}
private AuthenticationManager parentAuthenticationManager() {
//做数据校验
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(u2());
ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);
return providerManager;
}
UserDetailsService u2(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").build());
return inMemoryUserDetailsManager;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.authenticationManager(authenticationManager()).formLogin(f->f.permitAll()).csrf(c->c.disable());
return http.build();
}
}
ProviderManager 这个方法的构造器里面,本身就是提供,注入一个AuthenticationProvider,或者多个AuthenticationProvider以及他们的父类。
那按照我们上面的逻辑,第一个AuthenticationManager 认证不通过的时候,他就会去parent里面找,看还有没有其他认证,找到了就通过,找不到就继续调用parent,一直到找到,或者找不到parent了,认证还是不通过,就返回失败了。
结语
学海无涯,发量做舟,马上要秃。。。