其他分享
首页 > 其他分享> > springboot集成springSecurity(总结篇)

springboot集成springSecurity(总结篇)

作者:互联网

 

SpringSecurity核心是什么?

过滤器链

在这里插入图片描述

有哪些组件?

AuthenticationManager 认证管理器(所有的认证都是他负责完成)【注意它是一个接口,一般使用实现类是ProviderManager】

 

 

 

认证

SpringSecurity底层认证默认是通过UsernamePasswordAuthenticationFilter进行处理的

什么叫认证?

如何认证?

1、UsernamePasswordAuthenticationFilter
通过这个filter把登陆的用户名和密码拿到,组织一个权限对象(UsernamePasswordAuthenticationToken)只包含用户名和登录时,采集的密码
2、ProviderManager.authenticate(authRequest)调用权限认证器进行验证
2.1权限验证码内部执行authenticate验证方法
   拿到身份认证提供者(AuthenticationProvider集合)通过源码分析,里面只包含一个实例为DaoAuthenticationProvider
   provider.authenticate(authentication)的认证
   DaoAuthenticationProvider.authenticate(authentication)的认证方法
   最终调用了DaoAuthenticationProvider父类的AbstractUserDetailsAuthenticationProvider的authenticate方法
2.2AbstractUserDetailsAuthenticationProvider的authenticate逻辑
   如果你配置了userCache缓存策略,首先通过缓存找,如果找不到在来
   //检索查询user
   user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication)
   这里就拿到了用户信息(数据库或者缓存中拿的)
   this.preAuthenticationChecks.check(user);//验证用户状态【可用、密码过期、锁定、账号过期】
   additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);//进行密码匹配验证
   //验证通过最终又返回UsernamePasswordAuthenticationToken,这些把user的权限信息也放到UsernamePasswordAuthenticationToken里面去了
   //这笔执行完了,就回到了UsernamePasswordAuthenticationFilter的attemptAuthentication(如果没抛异常的话,那就successfulAuthentication调用认证成功方法了)
   //具体调用链路查看UsernamePasswordAuthenticationFilter父类(AbstractAuthenticationProcessingFilter)的doFilter方法
   return createSuccessAuthentication(principalToReturn, authentication, user);
   //最终实现由子类DaoAuthenticationProvider实现
2.3DaoAuthenticationProvider.retrieveUser
    //通过UserDetailsService接口实现类找,所以说为什么必须要去实现这个呢,因为这个是必须的
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username)

认证需要哪些环节参与?

(1)UsernamePasswordAuthenticationFilter 认证过滤器

(2)UsernamePasswordAuthenticationToken 携带的登录用户信息

(3)ProviderManager.authenticate(authRequest)调用权限认证器进行验证

(4)DaoAuthenticationProvider.retrieveUser

(5)通过UserDetailsService接口实现类找,所以说为什么必须要去实现这个呢,因为这个是必须的
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username)

如何自定义认证逻辑?

自定义认证逻辑很简单了,首先自定义登录过滤器,把登陆时,提供的用户名和密码收集到。然后调用认证器的逻辑进行认证;

授权

授权需要哪些环节参与

(1)BasicAuthenticationFilter默认的基础的验证过滤器

1、从请求header把里面携带的token拿到。然后解析得到具体的用户名称信息;

2、然后根据用户信息通过认证过程存储的用户权限信息拿出来;

3、然后把这些东西交给springSecurity,通过springSecurity给当前这个用户授予对应的权限;

4、实现BasicAuthenticationFilter最基础的权限过滤器接口,然后重写doFilterInternal方法;(方法实现思路,跟1-3步描述思路一致)

下面是自定义的授权过滤器

//根据request获取请求头里面携带的token,然后通过对token进行解析,拿到用户userName,然后在通过username去redis里面查询,如果找到了,把返回用户认证信息

需要在配置类把自定义的授权过滤器加进去

AuthenticationManager 认证管理器

在什么情况下需要使用身份(权限)验证器进行验证?

比如说在通过UsernamePasswordAuthenticationFitler

这个过滤器就是用于拦截登录逻辑的,也就是说你刚要登录,必须先经过我进行拦截;既然能够拦截,那么在当前拦截器里面就能够拿到用户客户端提交的用户号和密码;

在默认的UsernamePasswordAuthenticationFitler里面是通过把用户名和密码拿到,然后生成一个XXXToken,这个token其实就是一个身份信息是Authentication接口的实例;

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password)这个身份信息里面存了用户的名称和密码【记住,下面要考--哈哈】

然后把身份信息交给身份证(认证)管理器进行认证

this.getAuthenticationManager().authenticate(authRequest)【在看下面具体分析认证管理器是如何进行认证的】

然后最终认证成功,返回了UsernamePasswordAuthenticationToken,但是这个和登录时候的不是一个对象并且里面出了有用户名和密码信息,还包含认证成功后,存放的权限信息

this.getAuthenticationManager().authenticate(authRequest)【在看下面具体分析认证管理器是如何进行认证的】

首先脑补,自己想他可能会干什么。

(1)你既然是验证管理器,你肯定是执行的验证过程;

(2)反过来想,我只是把用户名和密码封装成身份信息传给你,然后交给你验证,那你咋验证?如何验证?这个就先看源码?.authenticate(authRequest)验证过程

开始上面说了getAuthenticationManager()拿到的实例就是ProviderManager,那我们就看ProviderManager的authenticate方法

划线的地方关键性分析

【1】在上面已经分析出来了,authentication的实例就是UsernamePasswordAuthenticationToken

//那我就可以理解authentication就是UsernamePasswordAuthenticationToken.class
Class<? extends Authentication> toTest = authentication.getClass()

【2】取到的是什么东西?
getProviders()
//身份验证提供者(你怎么来的)到底是什么实例
List<AuthenticationProvider> providers;
【通过查看构造函数发现,构造这个对象的时候必须提供一个或者多个身份验证提供者】
这个时候,我想看默认提供的是什么,那在哪里初始化提供的呢?【断点走一波】

下面分析完了providers只有一个就是DaoAuthenticationProvider(dao身份认证提供者)

DaoAuthenticationProvider这个里面包含了userDetailsService和passwordEncoder(如果存在时)

DaoAuthenticationProvider父类AbstractUserDetailsAuthenticationProvider
//是否支持,也就是说如果你是用户密码身份token的时候,我能支持;恰好在最开始使用认证管理器调用的验证方法的时候,提供的恰好就是UsernamePasswordAuthenticationToken的实例(可能是其子类--覆盖自定义)
public boolean supports(Class<?> authentication) {
    return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

构造方法要求必须提供一个身份验证码

【2.1】providers那个提供的

由配置类触发调用的

继续疑问,配置类里面的authenticationProviders哪里来的哟?【断点在看呗】

猜想,既然是配置类,那么肯定在配置类初始化的时候,进行设置的三。

找到入口

 

配置类初始化入口在这里?

【3】result = provider.authenticate(authentication)

其实执行的就是DaoAuthenticationProvider的authenticate方法(上面2里面有分析)

通过代码查看,起父类AbstractUserDetailsAuthenticationProvider中的authenticate方法【因为子类没实现--抽象父类默认实现】
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    //authentication就是【UsernamePasswordAuthenticationToken】里面携带的就是用户信息
    String username = determineUsername(authentication);//取出用户名
    boolean cacheWasUsed = true;
    //这里通过缓存查询是否存在,如果存在直接返回UserDetails
    UserDetails user = this.userCache.getUserFromCache(username);
    if (user == null) {
        cacheWasUsed = false;
        try {
            //查询用户信息【也是由子类完成】看下面
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (UsernameNotFoundException ex) {
            this.logger.debug("Failed to find user '" + username + "'");
            if (!this.hideUserNotFoundExceptions) {
                throw ex;
            }
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
    }
    try {
        //在身份验证之前先判断用户状态是不是正常的嘛isAccountNonLocked是否被锁定、isEnabled是否可用、isAccountNonExpired账号是否过期、isCredentialsNonExpired密码是否过期
        this.preAuthenticationChecks.check(user);
        //真正执行身份验证,拿到查询出来的user(数据库或者缓存等等),authentication通过登录获取的用户身份信息【UsernamePasswordAuthenticationToken】
        //这里其实就是拿到密码进行比较
        additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
    }
    catch (AuthenticationException ex) {
        if (!cacheWasUsed) {
            throw ex;
        }
        // There was a problem, so try again after checking
        // we're using latest data (i.e. not from the cache)
        cacheWasUsed = false;
        user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
        this.preAuthenticationChecks.check(user);
        additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
    }
    this.postAuthenticationChecks.check(user);
    if (!cacheWasUsed) {
        this.userCache.putUserInCache(user);
    }
    Object principalToReturn = user;
    if (this.forcePrincipalAsString) {
        principalToReturn = user.getUsername();
    }
    return createSuccessAuthentication(principalToReturn, authentication, user);
}

由子类DaoAuthenticationProvider实现的
protected void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    //如果证书为空,那就抛异常    
    if (authentication.getCredentials() == null) {
        this.logger.debug("Failed to authenticate since no credentials provided");
        throw new BadCredentialsException(this.messages
                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }
    //获取证书密码信息
    String presentedPassword = authentication.getCredentials().toString();
    //通过初始化设置的密码验证器进行匹配,匹配其实就是把登陆【UsernamePasswordAuthenticationToken存放的密码与查询出来的密码进行比较】如果比匹配就抛异常
    if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
        this.logger.debug("Failed to authenticate since password does not match stored value");
        throw new BadCredentialsException(this.messages
                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }
}

由子类DaoAuthenticationProvider实现的
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
    prepareTimingAttackProtection();
    try {
        //通过getUserDetailsService的实现类,调用loadUserByUsername方法,最终返回UserDetails
        UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        return loadedUser;
    }
    catch (UsernameNotFoundException ex) {
        mitigateAgainstTimingAttack(authentication);
        throw ex;
    }
    catch (InternalAuthenticationServiceException ex) {
        throw ex;
    }
    catch (Exception ex) {
        throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
    }
}

//自定义的userDetailsService
@Service//作为一个bean,因为在进行设置DaoAuthenticationProvider的时候,通过spring容器里面拿的。
public class WolfExtendUserDetailService implements UserDetailsService {
    //具体方法里面的实现
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户信息,不管从缓存或者任何地方吧用户信息及用户的权限信息拿出来
        //然后封装为UserDetails给返回回去【上一步的下面逻辑需要拿到UserDetails和UsernamePasswordAuthenticationToken里面的密码信息进行比对】
    }
}

【3.1】UserCache 缓存【有一篇专门的文章分析缓存--看完秒杀一切】

默认实现是NullUserCache

如果说后面想自定义实现userCache直接定义实例,设置到DaoAuthenticationProvider里面覆盖userCache即可;因为只有你实现了,就可以通过username去查询权限信息等等

 

 

 

 

 

标签:集成,username,authenticate,springboot,UsernamePasswordAuthenticationToken,认证,authe
来源: https://blog.csdn.net/qqzhengwei/article/details/114016899