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

}
相关推荐
风象南14 分钟前
SpringBoot的4种抽奖活动实现策略
java·spring boot·后端
結城9 小时前
mybatisX的使用,简化springboot的开发,不用再写entity、mapper以及service了!
java·spring boot·后端
Bruk.Liu9 小时前
《Minio 分片上传实现(基于Spring Boot)》
前端·spring boot·minio
星辰离彬10 小时前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
java·spring boot·后端·sql·mysql·性能优化
q_191328469510 小时前
基于Springboot+Vue的办公管理系统
java·vue.js·spring boot·后端·intellij idea
周全全12 小时前
基于 Vue 和 Spring Boot 实现滑块验证码的机器验证
前端·vue.js·spring boot
华子w90892585914 小时前
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
spring boot·微信小程序·uni-app
考虑考虑14 小时前
rollback-only事务
spring boot·后端·spring
脑瓜嗡15 小时前
Docker部署SpringBoot项目
spring boot·docker·容器
寒山李白15 小时前
Spring Boot 常用注解面试题深度解析
java·spring boot·面试·注解