springSecurity

博主因公司使用springSecurity用户认证框架,学习其运行过程。介绍了UsernamePasswordAuthenticationFilter过滤器处理认证逻辑,AuthenticationManager管理认证,DaoAuthenticationProvider检查密码、获取用户信息,还可自定义验证逻辑。最后说明SecurityContextHolder与认证判断的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一次写博客,由于公司用到了springSecurity 用户认证框架,简单的学习了一下运行过程.
流程图
在这里插入图片描述

 当用户第一次请求时会经过一个过滤器链 
   第一次登录请求  被SecurityContextPersistenceFilter拦截
       调用securityContextRepository 中的 loadContext()方法获取SecurityContext 

HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
    SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
将SecurityContext对象设置到SecurityContextHolder中 并放行.

在执行UsernamePasswordAuthenticationFilter过滤器,该过滤器是用来处理用户认证逻辑的.

 public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
	    private String usernameParameter = "username";
	    private String passwordParameter = "password";
	    private boolean postOnly = true;
    //只支持post请求
    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    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 {
          //根据参数名为"username"和"password"来获取用户名和密码的
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
          (3)  UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
          (4)  this.setDetails(request, authRequest);
           (5) return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
 (3)通过构造方法实例化一个UsernamePasswordAuthenticationToken对象,此时调用的是UsernamePasswordAuthenticationToken的两个参数的构造函数,如图:       


  public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

其中super(null)调用的是父类的构造方法,传入的是权限集合,因为目前还没有认证通过,所以不知道有什么权限信息,这里设置为null,然后将用户名和密码分别赋值给principal和credentials,同样因为此时还未进行身份认证,所以setAuthenticated(false)
`
(4)setDetails(request, authRequest)是将当前的请求信息设置到UsernamePasswordAuthenticationToken中

(5)通过调用getAuthenticationManager()来获取AuthenticationManager,通过调用它的authenticate方法来查找支持该token(UsernamePasswordAuthenticationToken)认证方式的provider,然后调用该provider的authenticate方法进行认证
2.AuthenticationManager是用来管理AuthenticationProvider的接口,通过AuthenticationManager 的实现类ProviderManager执行authenticate()通过for循环遍历AuthenticationProvider对象的集合,找到支持当前认证方式的AuthenticationProvider,找到之后调用该AuthenticationProvider的authenticate方法进行认证处理:
Iterator var6 = this.getProviders().iterator();
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}

                try {
                //执行authenticate方法进行认证处理
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var11) {
                    this.prepareException(var11, authentication);
                    throw var11;
                } catch (InternalAuthenticationServiceException var12) {
                    this.prepareException(var12, authentication);
                    throw var12;
                } catch (AuthenticationException var13) {
                    lastException = var13;
                }
            }
        }

3.DaoAuthenticationProvider
(1)查看additionalAuthenticationChecks附加检查方法,它主要是检查用户密码的正确性,如果密码为空或者错误都会抛出异常
(2)它是调用了getUserDetailsService先获取到UserDetailsService对象,通过调用UserDetailsService对象的loadUserByUsername方法获取用户信息UserDetails,找到UserDetailsService,发现它是一个接口,查看继承关系,有很多实现,都是spring-security提供的实现类,并不满足我们的需要,我们想自己制定获取用户信息的逻辑,所以我们可以实现这个接口。比如从我们的数据库中查找用户信息.
(3) ###如果我们还有其他逻辑信息的验证,我们可以自定义MyProvider 继承DaoAuthenticationProvider 实现authenticate()进行验证

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String captcha = request.getParameter(“captcha”);
String username = token.getName();
// UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(username);
Admin admin = adminMapper.findByEmail(username);
//验证用户是否存在
if(admin==null){
throw new AuthenticationServiceException(“该用户名不存在”);
}

账号状态验证
if(admin.getStatus().equals("1")){
    throw new AuthenticationServiceException("该用户已被禁用,请联系管理员");
}
// 验证码验证

if (!StringUtils.isNotEmpty(captcha)){
throw new SessionAuthenticationException(“验证码为空”);
}

if(!authCodeManage.verifyVerifyCode(username, EMailTplCodeDefine.VERIFY_CODE_ADMIN_LOGIN_VERIFY.getNo(),captcha)){
    throw new AuthenticationServiceException("验证码错误!");
}
Admin adminUpdate = new Admin();
// 验证密码是否正确
if (!new BCryptPasswordEncoder().matches((CharSequence) token.getCredentials(), userDetails.getPassword())) {

    if(admin.getLoginErrorCount()>=2){
        adminUpdate.setId(admin.getId());
        adminUpdate.setStatus("1");
        adminService.update(adminUpdate);
        throw new AuthenticationServiceException("密码错误");

    }
    adminUpdate.setId(admin.getId());
    adminUpdate.setLoginErrorCount(admin.getLoginErrorCount()+1);
    adminService.update(adminUpdate);
    throw new AuthenticationServiceException("密码错误");
}
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());

}

执行完后面的过滤并经过servlet处理之后,响应给浏览器之前再次经过此过滤器SecurityContextPersistenceFilter
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

通过SecurityContextHolder获取SecurityContext对象,然后清除SecurityContext,最后将获取的SecurityContext对象放入session中
其中SecurityContextHolder是与ThreadLocal绑定的,即本线程内所有的方法都可以获得SecurityContext对象,而SecurityContext对象中包含了Authentication对象,即用户的认证信息,spring-security判断用户是否认证主要是根据SecurityContext中的Authentication对象来判断
在这里插入图片描述

转载:https://blog.youkuaiyun.com/abcwanglinyong/article/details/80981389

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值