首页 > 编程语言 >[SpringSecurity5.6.2源码分析三]:SpringWebMvcImportSelector

[SpringSecurity5.6.2源码分析三]:SpringWebMvcImportSelector

时间:2023-09-06 17:33:28浏览次数:47  
标签:SpringSecurity5.6 return null class 源码 CsrfToken SpringWebMvcImportSelector para

1、SpringWebMvcImportSelector

  • • SpringSecurity支持在SpringMVC进行参数解析的时候填充参数,支持以下的对象
  • • 通过@AuthenticationPrincipal,获取UserDetails
  • • 通过@CurrentSecurityContext,获取SecurityContext
  • • 通过参数类型为CsrfToken获取CsrfToken
  • • 究其原因是因为SpringSecurity为这些参数类型注册了对应的参数解析器
  • • SpringWebMvcImportSelector源码如下:
class SpringWebMvcImportSelector implements ImportSelector {

   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      boolean webmvcPresent = ClassUtils.isPresent(
            "org.springframework.web.servlet.DispatcherServlet",
            getClass().getClassLoader());
      return webmvcPresent
            ? new String[] {
                  "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" }
            : new String[] {};
   }
}
  • • 分析可以看出当可以加载SpringMVC的DispatcherServlet的时候注册一个WebMvcSecurityConfiguration类

2、WebMvcSecurityConfiguration

  • • 此类作用如下:
  • • AuthenticationPrincipalArgumentResolver:针对@AuthenticationPrincipal,注意这里是两个名称相同并且支持的注解名称也一模一样的
  • • CurrentSecurityContextArgumentResolver:针对@CurrentSecurityContext
  • • CsrfTokenArgumentResolver:针对CsrfToken
  • • 注册四个参数解析器
  • • 注册CsrfRequestDataValueProcessor:当开启了Csrf的情况下,此类负责将Csrf添加到具有隐藏域的表单中
class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware {

   private BeanResolver beanResolver;

   @Override
   @SuppressWarnings("deprecation")
   public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
      authenticationPrincipalResolver.setBeanResolver(this.beanResolver);
      argumentResolvers.add(authenticationPrincipalResolver);
      argumentResolvers
            .add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
      CurrentSecurityContextArgumentResolver currentSecurityContextArgumentResolver = new CurrentSecurityContextArgumentResolver();
      currentSecurityContextArgumentResolver.setBeanResolver(this.beanResolver);
      argumentResolvers.add(currentSecurityContextArgumentResolver);
      // 注册 CsrfToken 的参数解析器
      argumentResolvers.add(new CsrfTokenArgumentResolver());
   }

   @Bean
   RequestDataValueProcessor requestDataValueProcessor() {
      return new CsrfRequestDataValueProcessor();
   }

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
   }

}

2.1 AuthenticationPrincipalArgumentResolver

  • • 这里仅介绍org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver,另外一个多了SpEL的解析方式
  • • 可以看出当方法入参中有携带@AuthenticationPrincipal的时候,会从线程级别的安全上下文中获取认证对象
@Deprecated
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {

   @Override
   public boolean supportsParameter(MethodParameter parameter) {
      return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
   }

   @Override
   public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
      // 通过线程级别的安全上下文获得认证对象
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authentication == null) {
         return null;
      }
      // 获得用户对象
      Object principal = authentication.getPrincipal();
      // 如果两者类型不匹配是否抛出异常
      if (principal != null && !parameter.getParameterType().isAssignableFrom(principal.getClass())) {
         AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
         if (authPrincipal.errorOnInvalidType()) {
            throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
         }
         return null;
      }
      return principal;
   }

   /**
    * 获得指定注解
    */
   private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
      T annotation = parameter.getParameterAnnotation(annotationClass);
      if (annotation != null) {
         return annotation;
      }
      Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
      for (Annotation toSearch : annotationsToSearch) {
         annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
         if (annotation != null) {
            return annotation;
         }
      }
      return null;
   }

}
  • • 这里的认证对象指的是Authentication,部分实现如下:
  • • UsernamePasswordAuthenticationToken:通过用户名和密码生成的认证对象
  • • RememberMeAuthenticationToken:通过记住我令牌生成的认证对象
  • • .......

2.2 CurrentSecurityContextArgumentResolver

  • • 支持解析标注了@CurrentSecurityContext注解的参数、
  • • eg:@CurrentSecurityContext(expression="authentication") Authentication authentication
  • • 支持Controller方法中的入参中有标注了@CurrentSecurityContext放在SecurityContext参数上
  • • 支持 Spring SpEl表达式从SecurityContext中获取值
public final class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {

   private ExpressionParser parser = new SpelExpressionParser();

   private BeanResolver beanResolver;

   /**
    * 此参数解析器只能支持带有 {@code CurrentSecurityContext} 注解的参数
    * @param parameter
    * @return
    */
   @Override
   public boolean supportsParameter(MethodParameter parameter) {
      return findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
   }

   @Override
   public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
      // 从线程级别的策略中拿到安全上下文
      SecurityContext securityContext = SecurityContextHolder.getContext();
      if (securityContext == null) {
         return null;
      }
      Object securityContextResult = securityContext;
      // 从参数上拿到指定的 CurrentSecurityContext 注解信息
      CurrentSecurityContext annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
      String expressionToParse = annotation.expression();
      // 是否以 SpEL 进行解析
      // SpEL 不懂
      if (StringUtils.hasLength(expressionToParse)) {
         StandardEvaluationContext context = new StandardEvaluationContext();
         context.setRootObject(securityContext);
         context.setVariable("this", securityContext);
         context.setBeanResolver(this.beanResolver);
         Expression expression = this.parser.parseExpression(expressionToParse);
         securityContextResult = expression.getValue(context);
      }
      // 如果有安全上下文,但是参数类型不对
      if (securityContextResult != null
            && !parameter.getParameterType().isAssignableFrom(securityContextResult.getClass())) {
         // 是否抛出异常,还是返回空
         if (annotation.errorOnInvalidType()) {
            throw new ClassCastException(
                  securityContextResult + " is not assignable to " + parameter.getParameterType());
         }
         return null;
      }
      return securityContextResult;
   }

   /**
    * Set the {@link BeanResolver} to be used on the expressions
    * @param beanResolver the {@link BeanResolver} to use
    */
   public void setBeanResolver(BeanResolver beanResolver) {
      Assert.notNull(beanResolver, "beanResolver cannot be null");
      this.beanResolver = beanResolver;
   }

   /**
    * 在指定的方法参数上,获得指定的注解
    * @param annotationClass the class of the {@link Annotation} to find on the
    * {@link MethodParameter}
    * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
    * @return the {@link Annotation} that was found or null.
    */
   private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
      // 拿到参数上的指定注解
      T annotation = parameter.getParameterAnnotation(annotationClass);
      if (annotation != null) {
         return annotation;
      }
      Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
      for (Annotation toSearch : annotationsToSearch) {
         annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
         if (annotation != null) {
            return annotation;
         }
      }
      return null;
   }

}

2.3 CsrfTokenArgumentResolver

  • • 源码很简单就是直接从请求域中获得CsrfToken
public final class CsrfTokenArgumentResolver implements HandlerMethodArgumentResolver {

   /**
    * 此参数解析器仅支持 {@code CsrfToken}
    * @param parameter
    * @return
    */
   @Override
   public boolean supportsParameter(MethodParameter parameter) {
      return CsrfToken.class.equals(parameter.getParameterType());
   }

   @Override
   public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
      // 从请求域中获得CsrfToken, 此属性值是由CsrfFilter负责放入的
      CsrfToken token = (CsrfToken) webRequest.getAttribute(CsrfToken.class.getName(),
            RequestAttributes.SCOPE_REQUEST);
      return token;
   }

}
  • • 至于为什么在请求域中有CsrfToken,下面的代码能看出是在SpringSecurity的CsrfFilter中负责将CsrfToken放到请求域中的
public final class CsrfFilter extends OncePerRequestFilter {
  ......
  @Override
  protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response, FilterChain filterChain)
              throws ServletException, IOException {
     .......
     request.setAttribute(CsrfToken.class.getName(), csrfToken);
     ......
  }
.......
}
  • • 又衍生出一个问题,到底是setAttribute还是getAttribute先执行呢
  • • 下图能够看出应用程序中一共有五个过滤器,前三个是SpringMVC提供的,第四个就是FilterChainProxy也就是SpringSecurity的过滤器链,CsrfFilter就是在这里面执行的,而参数解析器是在DispatcherServlet中负责执行的,而DispatcherServlet最终是这里的第五个过滤器中负责调用的
  • • 所以说一定是先是setAttribute还再getAttribute
  • [SpringSecurity5.6.2源码分析三]:SpringWebMvcImportSelector_java


标签:SpringSecurity5.6,return,null,class,源码,CsrfToken,SpringWebMvcImportSelector,para
From: https://blog.51cto.com/u_14008019/7388886

相关文章

  • Spring源码分析(十一)ApplicationContext详细介绍(上)
    在前面的文章中,已经完成了官网关于IOC内容核心的部分。包括容器的概念,Spring创建bean的模型BeanDefinition的介绍容器的扩展点(BeanFactoryProcessor,FactoryBean,BeanPostProcessor)以及最重要的bean的生命周期等。接下来大概还有花三篇文章完成对官网中第一大节的其他内容,之所以要......
  • 二级医院信息系统源码(HIS) Angular+Nginx+ Java
    基层医疗云HIS作为基于云计算的B/S构架的HIS系统,为基层医疗机构提供了标准化的、信息化的、可共享的医疗信息管理系统,可有效进行医疗数据共享与交换,解决数据重复采集及信息孤岛等问题,实现对基层医疗数据的分析和挖掘,为基层卫生机构提供科学合理的业务管理服务。可实现“云部署”,即......
  • android源码分析1--updater(l上)
    一install.cpp中调用updater:constchar*binary="/tmp/update_binary";constchar**args=(constchar**)malloc(sizeof(char*)*5);args[0]=binary;args[1]=EXPAND(RECOVERY_API_VERSION);//definedinAndroid.mkchar*temp=......
  • aosp源码分析 5.0 BlockImageUpdateFn
    block_image_update("/dev/block/bootdevice/by-name/system",package_extract_file("system.transfer.list"),"system.new.dat","system.patch.dat");//args://-blockdevice(orfile)tomodifyin-place......
  • 直播系统源码部署,高效文件管理与传输的FTP协议
    引言: 在直播系统源码部署的过程中,开发协议是支持直播系统源码功能技术搭建成功并发挥作用的关键之一,在直播系统源码的众多协议中,有一个协议可以帮助直播系统源码部署完成后用户进行媒体文件的上传、下载、管理等操作,这个协议就是FTP协议,本文就将具体介绍直播系统源码的FTP协议......
  • 自适应红色大气虚拟手机靓号交易商城网站源码
       靓号虚拟商城源码跟之前发布的有点不同的就是,这个是用于做手机靓号交易平台网站的,   但从布局跟设计来看我更喜欢今天发的这个,UI看上去是两年前的流行样式,    但是只需要简单的修改下CSS就是一个大气的手机靓号交易平台网站,源码带手机版,以及手机靓号回收功能......
  • 自适应红色大气虚拟手机靓号交易商城网站源码
    源码分享:靓号虚拟商城源码跟之前发布的有点不同的就是,之前的是做靓号的二这个是用于做手机靓号交易平台网站的。但从布局跟设计来看我更喜欢今天发的这个,UI看上去是两年前的流行样式但是只需要简单的修改下CSS就是一个大气的手机靓号交易平台网站,源码带手机版,以及手机靓号回收功能......
  • ECshop仿顺丰优选综合购物商城平台源码旗舰版+团购+触屏版
    源码介绍:一款时尚简洁的综合通用类模板,整站宽屏,头部含多个下拉菜单、购物车及搜索功能,方便扫描;首页商品楼层功能,调用限时抢购和下期限时抢购功能,底部调用评论功能,添加了邮件订阅。二级分类页新增自定义组合筛选、商品排序、翻页等功能;商品详情页开发单选属性、数量加减、剩余件数、......
  • 【全套】源支付5.18最新版协议去授权全套三端开源源码_客户端+云端+监控+协议三网免挂
    推荐系统为:CentOS7.6Linux系统环境:Nginx1.20.1+MySQL5.6.50+PHP-7.2+Redis将商户后台源码上传解压运行目录为Public伪静态为thinkphp访问域名傻瓜模式安装后台安装完了sudorpm-Uvhhttps://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm完成后输......
  • 10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)
    10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)前言上篇文章15000字、6个代码案例、5个原理图让你彻底搞懂Synchronized有说到synchronized由objectmonitor实现的objectmonitor中由cxq栈和entrylist来实现阻塞队列,waitset实现等待队列,从而实现synchronized的等待/通知......