springSecurity认证流程
- 基于EL-ADMIN项目,剖析验证过程
spring-security的认证机制
- 第一步获取登录时候的用户和密码
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userInfo.getUsername(), userInfo.getPassword());
这一步如果是自己写的controller就是这么写,其实springSecurity里面是有一个Filter(UsernamePasswordAuthenticationFilter)
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 : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
UsernamePasswordAuthenticationFilter的这个public Authentication attemptAuthentication()方法通过从表单或者ajax中获取的用户名和密码信息,然后返回一个Authentication对象,用于之后从缓存或者数据库读出来的真实数据进行对比
- 进行登录验证
- 有一点需要说明的是AuthenticationManagerBuilder的自动注入问题,具体看bloghttps://www.cnblogs.com/felordcn/p/14325124.html 说的是在AuthenticationConfiguration这个配置类中注入的(我没看懂)
- authenticationManagerBuilder.getObject()获取的是AuthenticationManager对象
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
3. authenticationManager.authenticate(authenticationToken)方法的调用
AuthenticationManager是一个接口,调用的是它的实现类ProviderManager中的authenticate()方法
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 {
// 这里调用AuthenticationProvider provider的authenticate()方法
// AuthenticationProvider是一个接口调用的实际上是AbstractUserDetailsAuthenticationProvider 的authenticate()
// 详细的代码见下面
// 后续就是利用传入的authentication和resulet对象进行比较看是否正确了
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;
}
}
AbstractUserDetailsAuthenticationProvider 的authenticate()的调用
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
// 从传进来的authentication对象中取出username
String username = this.determineUsername(authentication);
boolean cacheWasUsed = true;
// 先从缓存中尝试是否有这个username的用户的存在
UserDetails user = this.userCache.getUserFromCache(username);
// 如果没有就会使用retrieveUser()方法,这个方式实质上就是去调用loadUserByUsername()方法
if (user == null) {
cacheWasUsed = false;
try {
// 详细见下面的代码
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw var6;
}
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
retrieveUser()方法的代码
// 这个是AbstractUserDetailsAuthenticationProvider这个抽象类的抽象方法,实际上是调用的其子类DaoAuthenticationProvider的方法重写
// protected abstract UserDetails retrieveUser(String var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException;
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
// 这里就是调用的UserDetailService这个接口里面的loadUserByUsername(String s)方法.
// loadUserByUsername(String s)是我们自己写的一个实现类。逻辑大概就是从缓存或者是从数据库查找,返回一个UserDetail的对象
// UserDetail是一个接口,其中spring-security自己里面实现的一个类是User,也可以自定义
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserInfo userInfo = userInfodao.findByUsername(s);
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
if(userInfo.getRole() != null){
list.add(new SimpleGrantedAuthority(userInfo.getRole()));
}
return new User(userInfo.getUsername(), new BCryptPasswordEncoder().encode(userInfo.getPassword()),list);
}
- 验证成功之后将Authentication对象存到SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authentication);
该blog参考了https://www.cnblogs.com/ymstars/p/10626786.html