[SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration

1. WebMvcSecurityConfiguration

  • 在SpringSecurtiy中有一些参数是可以在SpringMVC中直接进行自动装配的,就像下面这三个
  • 而这些能够起作用正是因为WebMvcSecurityConfiguration注册了这三个参数对应的参数解析器
  • 下面是此配置类的导入链路和源码
java 复制代码
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());
   }

}
  • 通过分析源码我们可以知道这里导入了四个参数解析器,但是实际上是三种不同类型的参数解析器
    • AuthenticationPrincipalArgumentResolver
    • CurrentSecurityContextArgumentResolver
    • CsrfTokenArgumentResolver

2. SpringSecurity中是如何注册参数解析器

  • 要讲注册的这些参数解析器是如何工作的得先介绍SpringMVC是如何识别到SpringSecurity注册的参数解析的
  • 先看下图,这是SpringMVC的自动配置类
  • 而在EnableWebMvcConfiguration中有一个addArgumentResolvers(...)方法,可以看到这里就有我们注册的WebMvcSecurityConfiguration了
  • 然后我们再看下面这个方法,这里很明显是将容器中的WebMvcConfigurer注册到当前类中
  • 所以说WebMvcSecurityConfiguration才实现了WebMvcConfigurer

3. AuthenticationPrincipalArgumentResolver

  • SpringSecurity中注册了两个名称一样但是包路径不一样的参数解析器,但是这两个参数解析器的作用是大抵相同的
  • 任何参数解析器都是由下面两大核心销方法组成的
java 复制代码
public interface HandlerMethodArgumentResolver {

   /**
    * 判断此参数解析器是否支持此参数
    */
   boolean supportsParameter(MethodParameter parameter);

   /**
    * 解析入参,并返回参数值
    */
   @Nullable
   Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}
  • 然后我们再看AuthenticationPrincipalArgumentResolver
java 复制代码
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();
      AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
      String expressionToParse = annotation.expression();
      // 解析表达式值
      if (StringUtils.hasLength(expressionToParse)) {
         StandardEvaluationContext context = new StandardEvaluationContext();
         context.setRootObject(principal);
         context.setVariable("this", principal);
         context.setBeanResolver(this.beanResolver);
         Expression expression = this.parser.parseExpression(expressionToParse);
         principal = expression.getValue(context);
      }
      if (principal != null && !ClassUtils.isAssignable(parameter.getParameterType(), principal.getClass())) {
         if (annotation.errorOnInvalidType()) {
            throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
         }
         return null;
      }
      return principal;
   }
   ...
}
  • 分析源码可知:
    • 此参数解析器只支持@AuthenticationPrincipal
    • @AuthenticationPrincipal可以支持自动装配认证对象中的主体(用户对象),以及可以通过SpelExpressionParser自动装配用户对象中的属性

4. CurrentSecurityContextArgumentResolver

  • CurrentSecurityContextArgumentResolver:
    • 支持Controller中的方法中的入参中有标注了@CurrentSecurityContext放在SecurityContext参数上
    • 支持SpringSpEl表达式从SecurityContext中获取值
      • eg:@CurrentSecurityContext(expression="authentication") Authentication authentication
java 复制代码
public final class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
   ...
   /**
    * 此参数解析器只能支持带有 {@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 进行解析
      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;
   }
   ...
}

5. CsrfTokenArgumentResolver

  • CsrfTokenArgumentResolver:为了解析方法入参中有CsrfToken的参数解析器
java 复制代码
public final class CsrfTokenArgumentResolver implements HandlerMethodArgumentResolver {

   /**
    * 此参数解析器仅支持 {@code CsrfToken}
    */
   @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;
   }

}
相关推荐
qq_12498707534 小时前
基于微信小程序的电子元器件商城(源码+论文+部署+安装)
java·spring boot·spring·微信小程序·小程序·毕业设计
资生算法程序员_畅想家_剑魔4 小时前
Java常见技术分享-11-责任链模式
java·spring boot·责任链模式
计算机程序设计小李同学5 小时前
动漫之家系统设计与实现
java·spring boot·后端·web安全
JIngJaneIL6 小时前
基于springboot + vue健康管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端
刘一说6 小时前
Spring Boot中IoC(控制反转)深度解析:从实现机制到项目实战
java·spring boot·后端
悟空码字6 小时前
SpringBoot参数配置:一场“我说了算”的奇幻之旅
java·spring boot·后端
其美杰布-富贵-李6 小时前
Java (Spring Boot) 反射完整学习笔记
java·spring boot·学习
计算机毕设VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue医院挂号管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
码农小卡拉9 小时前
Java多线程:CompletableFuture使用详解(超详细)
java·开发语言·spring boot·python·spring·spring cloud
I'm Jie9 小时前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven