[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;
   }

}
相关推荐
悟空码字1 天前
Spring Boot 整合 MongoDB 最佳实践:CRUD、分页、事务、索引全覆盖
java·spring boot·后端
皮皮林5513 天前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602735 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840826 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解6 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解6 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记6 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者7 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840827 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解7 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端