首页 > 其他分享 >SpringSecurity原理解析(二):认证流程

SpringSecurity原理解析(二):认证流程

时间:2024-09-12 21:51:31浏览次数:3  
标签:null request Authentication 认证 authentication user SpringSecurity 解析

1、SpringSecurity认证流程包含哪几个子流程?


     1)账号验证


     2)密码验证


     3)记住我—>Cookie记录


     4)登录成功—>页面跳转


2、UsernamePasswordAuthenticationFilter


     在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤


     器中实现的,UsernamePasswordAuthenticationFilter 继承于


     AbstractAuthenticationProcessingFilter 这个父类。


     当请求进来时,在doFilter 方法中会对请求进行拦截,判断请求是否需要认证,若不需要


     认证,则放行;否则执行认证逻辑;


             1


             


     注意:UsernamePasswordAuthenticationFilter 类中是没有 doFilter 方法的,doFilter


     方法是继承自父类 UsernamePasswordAuthenticationFilter 的。


           doFilter 方法代码如下:


//执行过滤的方法,所有请求都走这个方法

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

       //请求和应答类型转换

       HttpServletRequest request = (HttpServletRequest)req;

       HttpServletResponse response = (HttpServletResponse)res;

       //判断请求是否需要认证处理,若不需要认证处理,则直接放行

       if (!this.requiresAuthentication(request, response)) {

           //放行,往下走

           chain.doFilter(request, response);

       } else {

           //执行到这里,进行认证处理

           if (this.logger.isDebugEnabled()) {

               this.logger.debug("Request is to process authentication");

           }

           Authentication authResult;

           try {

               //处理认证,然后返回 Authentication 认证对象

               //重点

               authResult = this.attemptAuthentication(request, response);

               //认证失败

               if (authResult == null) {

                   return;

               }

               //认证成功之后注册session

               this.sessionStrategy.onAuthentication(authResult, request, response);

           } catch (InternalAuthenticationServiceException var8) {

               this.logger.error("An internal error occurred while trying to authenticate the user.", var8);

               this.unsuccessfulAuthentication(request, response, var8);

               return;

           } catch (AuthenticationException var9) {

               this.unsuccessfulAuthentication(request, response, var9);

               return;

           }

           

           //

           if (this.continueChainBeforeSuccessfulAuthentication) {

               chain.doFilter(request, response);

           }

           //认证成功后的处理

           this.successfulAuthentication(request, response, chain, authResult);

       }

   }

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

       if (this.logger.isDebugEnabled()) {

           this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);

       }

       //将认证成功后的对象保存到 SecurityContext中

       SecurityContextHolder.getContext().setAuthentication(authResult);

       //处理 remember-me属性

       this.rememberMeServices.loginSuccess(request, response, authResult);

       if (this.eventPublisher != null) {

           this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));

       }

       //认证成功后,页面跳转

       this.successHandler.onAuthenticationSuccess(request, response, authResult);

   }


             


     上边的核心代码是下边这一行:


           


      attemptAuthentication方法的作用是获取Authentication对象其实就是对应的认证过程,


      attemptAuthentication 方法在子类UsernamePasswordAuthenticationFilter 中实现的。


       attemptAuthentication 方法代码如下:


//认证逻辑

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

       //如果我们设置了该认证请求只能以post方式提交,且当前请求不是post请求,表示当前请求不符合

       //认证要求,直接抛出异常,认证失败

       if (this.postOnly && !request.getMethod().equals("POST")) {

           throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());

       } else {

           //执行到这里表示开始执行认证逻辑

           //从请求中获取用户名和密码

           String username = this.obtainUsername(request);

           String password = this.obtainPassword(request);

           if (username == null) {

               username = "";

           }

           if (password == null) {

               password = "";

           }

           username = username.trim();

           //将用户名和密码包装成 UsernamePasswordAuthenticationToken  对象

           UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

           //设置用户提交的信息到 UsernamePasswordAuthenticationToken  中

           this.setDetails(request, authRequest);

           //getAuthenticationManager():获取认证管理器

           //authenticate:真正处理认证的方法

           return this.getAuthenticationManager().authenticate(authRequest);

       }

   }


       1、


3、AuthenticationManager


    AuthenticationManager接口中就定义了一个方法authenticate方法,用于处理认证的请求;


    AuthenticationManager 接口定义如下:


public interface AuthenticationManager {

   //处理认证请求

Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

   


     在这里AuthenticationManager的默认实现是ProviderManager.而在ProviderManager的


     authenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers


     。该providers中如果有一个AuthenticationProvider的supports函数返回true,那么就会调


     用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个认证过程结束。


     如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证


     成功则为认证成功。


     authenticate 方法定义如下:


           


//执行认证逻辑

public Authentication authenticate(Authentication authentication)

  throws AuthenticationException {

       //获取 Authentication 对象的类型

 Class<? extends Authentication> toTest = authentication.getClass();

 AuthenticationException lastException = null;

 AuthenticationException parentException = null;

 Authentication result = null;

 Authentication parentResult = null;

 boolean debug = logger.isDebugEnabled();

       //getProviders():获取系统支持的各种认证方式,如:QQ、微信、微博等等

 for (AuthenticationProvider provider : getProviders()) {

           //判断当前的 provider认证处理器 是否支持当前请求的认证类型,若不支持,则跳过

  if (!provider.supports(toTest)) {

   continue;

  }

  if (debug) {

   logger.debug("Authentication attempt using "

     + provider.getClass().getName());

  }

           //执行到这里说明当前认证处理器支持当前请求的认证,

  try {

               //执行认证操作

   result = provider.authenticate(authentication);

   if (result != null) {

    copyDetails(authentication, result);

    break;

   }

  }

  catch (AccountStatusException e) {

   //。。。。。省略 。。。。。

  }

  catch (InternalAuthenticationServiceException e) {

   //。。。。。省略 。。。。。

  }

  catch (AuthenticationException e) {

   //。。。。。省略 。。。。。

  }

 }

       //如果循环结束后还没找到支持当前请求的认证处理器provider ,且父类不为空,则

       //尝试调用父类的认证方法进行认证处理

 if (result == null && parent != null) {

  // Allow the parent to try.

  try {

   result = parentResult = parent.authenticate(authentication);

  }

  catch (ProviderNotFoundException e) {

   //。。。。。省略 。。。。。

  }

  catch (AuthenticationException e) {

   //。。。。。省略 。。。。。

  }

 }

       //清空密码凭证

 if (result != null) {

  if (eraseCredentialsAfterAuthentication

    && (result instanceof CredentialsContainer)) {

   // Authentication is complete. Remove credentials and other secret data

   // from authentication

   ((CredentialsContainer) result).eraseCredentials();

  }

  //

  if (parentResult == null) {

   eventPublisher.publishAuthenticationSuccess(result);

  }

  return result;

 }

 //

       //异常处理

 if (lastException == null) {

  lastException = new ProviderNotFoundException(messages.getMessage(

    "ProviderManager.providerNotFound",

    new Object[] { toTest.getName() },

    "No AuthenticationProvider found for {0}"));

 }

 //

 if (parentException == null) {

  prepareException(lastException, authentication);

 }

 throw lastException;

}


      在上边的代码中,我们重点看的是下边这一行:


             result = provider.authenticate(authentication);


      因为是用户认证,所以这里authenticate方法走是AbstractUserDetailsAuthenticationProvider


      类中的实现,


     AbstractUserDetailsAuthenticationProvider.authenticate 方法定义如下所示:


//认证操作

public Authentication authenticate(Authentication authentication)

  throws AuthenticationException {

 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,

   () -> messages.getMessage(

     "AbstractUserDetailsAuthenticationProvider.onlySupports",

     "Only UsernamePasswordAuthenticationToken is supported"));

 // 获取提交的账号

 String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"

   : authentication.getName();

       //标记,是否使用缓存,默认是使用的,先从缓存中查找提交的账号

       //若账号已经登录,则缓存中应该

 boolean cacheWasUsed = true;

       //根据账号名称从缓存中查找账号,若缓存中不存在该账号,则需要认证

 UserDetails user = this.userCache.getUserFromCache(username);

 if (user == null) {//若缓存中不存在该账号,没有缓存

  cacheWasUsed = false;

  try {

               //账号认证

   user = retrieveUser(username,

     (UsernamePasswordAuthenticationToken) authentication);

  }

  catch (UsernameNotFoundException notFound) {

   //。。。。。省略 。。。。。

  }

  Assert.notNull(user,

    "retrieveUser returned null - a violation of the interface contract");

 }

 try {

           //如果账号存在,即账号认证成功,则这里就开始密码认证

           //密码校验前的前置检查,检查账号是否过期、是否锁定等

  preAuthenticationChecks.check(user);

           //密码校验

           //user: 数据库中的数据

           //authentication: 表单提交的数据

  additionalAuthenticationChecks(user,

    (UsernamePasswordAuthenticationToken) authentication);

 }

 catch (AuthenticationException exception) {

  //。。。。。省略 。。。。。

 }

       //检查凭证是否过期

       postAuthenticationChecks.check(user);

     

       //将用户保存到缓存中

 if (!cacheWasUsed) {

  this.userCache.putUserInCache(user);

 }

 Object principalToReturn = user;

 if (forcePrincipalAsString) {

  principalToReturn = user.getUsername();

 }

 return createSuccessAuthentication(principalToReturn, authentication, user);

}

//创建具体的 Authentication 对象

protected Authentication createSuccessAuthentication(Object principal,

  Authentication authentication, UserDetails user) {

 

 // user.getAuthorities():返回用户的权限

 UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(

   principal, authentication.getCredentials(),

   authoritiesMapper.mapAuthorities(user.getAuthorities()));

 result.setDetails(authentication.getDetails());

 return result;

}


             密码前置校验如下图所示:


                     


     然后进入到retrieveUser方法中,retrieveUser和additionalAuthenticationChecks 方法


     具体的实现是DaoAuthenticationProvider 类中实现的,如下所示


@Override

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)

  throws AuthenticationException {

 prepareTimingAttackProtection();

 try {

           // getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了

  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) {

  //。。。。。省略 。。。。。

 }

 catch (InternalAuthenticationServiceException ex) {

  //。。。。。省略 。。。。。

 }

 catch (Exception ex) {

  //。。。。。省略 。。。。。

 }

}

//具体的密码校验逻辑

protected void additionalAuthenticationChecks(UserDetails userDetails,

  UsernamePasswordAuthenticationToken authentication)

  throws AuthenticationException {

       //凭证为空(即密码没传进来),则直接抛出异常

 if (authentication.getCredentials() == null) {

  logger.debug("Authentication failed: no credentials provided");

  throw new BadCredentialsException(messages.getMessage(

    "AbstractUserDetailsAuthenticationProvider.badCredentials",

    "Bad credentials"));

 }

       //获取表单提交的密码

 String presentedPassword = authentication.getCredentials().toString();

       //拿表单提交的密码,与数据库中的密码进行匹配,若匹配失败,则抛出异常

 if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {

  logger.debug("Authentication failed: password does not match stored value");

  throw new BadCredentialsException(messages.getMessage(

    "AbstractUserDetailsAuthenticationProvider.badCredentials",

    "Bad credentials"));

 }

}


标签:null,request,Authentication,认证,authentication,user,SpringSecurity,解析
From: https://blog.51cto.com/JiangJinHao/11993691

相关文章

  • 第33次CSP认证模拟的教训
    在写第三题化学方程式配平的时候,我用的是Python,决定写两个类来更好的实现逻辑。主要就是Array类,能像numpy那样对整个数组的元素进行操作。但是写完之后运行总报错有None参与运算或者Array不支持除法等(我寻思着有实现__div__啊)以下是我收获的教训:1.魔法方法的增量赋值运算符......
  • 出发前往玛那之树!《圣剑传说Visions of Mana》二十六项风灵月影修改器使用教程以及功
    《圣剑传说》v1.0二十六项修改器风灵月影版功能强大。不仅有无敌模式、无限HP/MP,还能设置攻击范围、暴击率等参数。更能一键编辑金钱、经验值。有了它,能大幅降低游戏难度,让玩家轻松畅玩,享受更愉悦的游戏体验。本篇将为大家带来《圣剑传说VisionsofMana》二十六项风灵月影修改......
  • 登上银河帝国系的最高通缉名单!《星球大战:亡命之徒》风灵月影版二十六项修改器使用方法
    《星球大战:亡命之徒》v1.0二十六项修改器风灵月影版能够一键开启26项强大的游戏内功能,诸如无敌模式、一击必杀以及隐身模式等等。此修改器专为优化玩家的星际冒险历程而打造,能助您在游戏里毫无阻碍,领略到极致的愉悦。本篇将为大家带来《星球大战:亡命之徒》风灵月影版二十六项修......
  • 详解新规|逐条剖析《电子政务电子认证服务管理办法》
    《电子政务电子认证服务管理办法》已在2024年8月26日国家密码管理局局务会议审议通过,并于9月10在国家密码管理局官网正式公布,自2024年11月1日起施行。来源|公开资料图源|Pixabay编辑|公钥密码开放社区为了规范电子政务电子认证服务行为,对电子政务电子认证服务机构实施监督管理,保障电子......
  • ccie和hcie要怎么选?摸清高级认证的选择技巧
    当下,众多大学生都期望能够充分借助在校的充裕时光去考取相关证书,如此一来,待到毕业寻觅工作之际便能增添一项有力的加分筹码。网络工程师这一职业,在当下可谓是炙手可热,备受大学生们的青睐。而ccie和hcie作为IT领域极具权威性的专业认证,毫无疑问受到了大家的热切关注。那么,究竟......
  • 详解新规|逐条分析《电子认证服务管理办法(征求意见稿)》修订重点
    近日,工信部就《电子认证服务管理办法(征求意见稿)》公开征求意见。来源|公开资料图源|Pixabay编辑|公钥密码开放社区《电子认证服务管理办法》(以下简称《办法》)于2009年2月18日由中华人民共和国工业和信息化部发布,并在2015年4月29日进行了修订。该《办法》包括总则、电子认证服务机构、......
  • 什么是Carplay认证?什么样的Carplay认证过程?
    在当今的数字化时代,汽车不再仅仅是交通工具,更是人们生活中不可或缺的一部分。随着智能科技的不断发展,车载系统逐渐变得智能化和互联化。其中,CarPlay作为苹果公司推出的一款车载系统,凭借其简洁、直观和安全的操作体验,赢得了广大车主的喜爱。然而,要想让自己的车辆支持CarPlay功能,并不......
  • AndroidAuto认证难吗?需要准备哪些资料?
    在当前的数字化时代,汽车不再仅仅是交通工具,更是集成了众多智能技术的移动空间。其中,AndroidAuto作为连接手机与车载系统的桥梁,为驾驶者提供了更加便捷、智能的驾驶体验。然而,对于许多汽车制造商和车载系统开发者来说,AndroidAuto认证成为了一个绕不开的话题。那么,AndroidAuto认证......
  • ISO 9001认证:驱动企业高效管理与市场信任的双引擎
    ISO9001认证的最大优势之一,就是通过标准化管理,帮助企业从根本上提升运营效率。它要求企业在管理中设置清晰的流程和责任分工,从而避免了由于管理不当、流程不畅所导致的运营混乱。这种系统化的管理模式不仅让企业能够快速适应市场变化,还能够减少产品返工、次品和流程中的浪费现象,帮......
  • C 语言内存管理语法全解析(malloc、calloc、free)
    目录一、引言二、动态内存分配1.malloc函数2.calloc函数 3.realloc函数 三、内存释放 1.free函数 2.内存泄漏的避免四、内存管理的最佳实践1.检查内存分配的返回值2.避免内存访问越界  3.释放内存的顺序4.使用内存管理工具五、总结 一、引言   ......