SpringSecurity 验证流程

219 阅读3分钟

不适合学习使用,仅作为个人工具,大部分内容引用大佬文章

总体流程图

image.png

image.png 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动态鉴权流程解析
没什么写的,大佬文章写的很精辟没什么废话。