likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 23

1. WebMvcSecurityConfiguration

  • 在SpringSecurtiy中有一些参数是可以在SpringMVC中直接进行自动装配的,就像下面这三个 [SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration
  • 而这些能够起作用正是因为WebMvcSecurityConfiguration注册了这三个参数对应的参数解析器
  • 下面是此配置类的导入链路和源码 [SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration
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的自动配置类 [SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration
  • 而在EnableWebMvcConfiguration中有一个addArgumentResolvers(...)方法,可以看到这里就有我们注册的WebMvcSecurityConfiguration了 [SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration
  • 然后我们再看下面这个方法,这里很明显是将容器中的WebMvcConfigurer注册到当前类中 [SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration
  • 所以说WebMvcSecurityConfiguration才实现了WebMvcConfigurer [SpringSecurity5.6.2源码分析二十八]:WebMvcSecurityConfiguration

3. AuthenticationPrincipalArgumentResolver

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

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

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

}
  • 然后我们再看AuthenticationPrincipalArgumentResolver
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
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的参数解析器
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;
   }

}