不适合学习使用,仅作为个人工具,大部分内容引用大佬文章
总体流程图
Spring Security总体流程、认证流程、鉴权流程浅析
总体上分为认证与授权
认证
必看大佬文章
最简单易懂的Spring Security 身份认证流程讲解 - 曾俊杰的专栏
SpringSecurity+JWT认证流程解析
执行流程
1.UsernamePasswordAuthenticationFilter
经过一些列过滤后调用UsernamePasswordAuthenticationFilter,传入用户名、密码等信息生成UsernamePasswordAuthenticationToken,将token返回给AuthenticationManager进行验证。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
//1.匹配URL和Method
super(new AntPathRequestMatcher("/login", "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
//啥?你没有用POST方法,给你一个异常,自己反思去
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
//从请求中获取参数
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
//我不知道用户名密码是不是对的,所以构造一个未认证的Token先
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
//顺便把请求和Token存起来
this.setDetails(request, token);
//Token给谁处理呢?当然是给当前的AuthenticationManager喽
return this.getAuthenticationManager().authenticate(token);
}
}
}
2.AuthenticationManager
AuthenticationManager会注册多种AuthenticationProvider,用于验证,例如UsernamePassword对应的DaoAuthenticationProvider
public interface AuthenticationManager {
//验证完成的权限类存到SecurityContext
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
AuthenticationManager默认实现之ProviderManager详解
基本代码,完整解析见链接文章
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
Authentication result = null;
Authentication parentResult = null;
//得到Provider
Iterator var9 = this.getProviders().iterator();
//遍历Provider
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
//判断是否支持当前 `Authentication` ,只有支持当前 `Authentication` 请求的 `AuthenticationProvider` 才会继续后续逻辑处理。
if (provider.supports(toTest)) {
......
try {
//调用provider进行验证
result = provider.authenticate(authentication);
//验证成功则复制属性到result,退出循环方法
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (......){
......
}
}
}
//如果认证结果为 `null`,且存在父 `AuthenticationManager`,则调用父 `AuthenticationManager` 进行同样的身份认证操作,其处理逻辑基本同上。
if (result == null && this.parent != null) {
......
}
//result不为空,返回result
if (result != null) {
//擦除details,不知道干啥的
......
//英文字面意思,但不知道后续逻辑
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
.....
}
}
3.AuthenticationProvider 具体的验证工作
public interface AuthenticationProvider {
//验证工作
Authentication authenticate(Authentication var1) throws AuthenticationException;
//判断是否支持该类
boolean supports(Class<?> var1);
}
DaoAuthenticationProvider 在这调用了自定义的loadUserByUserName
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//熟悉的supports,需要UsernamePasswordAuthenticationToken
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//取出Token里保存的值
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
//从缓存取
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
//啥,没缓存?使用retrieveUser方法获取呀,调用loaduserbyusername
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
}
//...删减了一大部分,这样更简洁
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
try {
//熟悉的loadUserByUsername
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);
}
}
//检验密码
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
}
到此为止,就完成了用户名密码的认证校验逻辑,根据认证用户的信息,系统做相应的Session持久化和Cookie回写操作。
4.UserDetailsService
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
鉴权
大佬文章
SpringSecurity动态鉴权流程解析
没什么写的,大佬文章写的很精辟没什么废话。