[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 小时前
Java OpenFeign:微服务通信的“魔法契约书”
java·spring boot·spring cloud
_码农121385 小时前
spring boot 使用mybatis简单连接数据库+连表查询
数据库·spring boot·mybatis
麦兜*5 小时前
Spring Boot 整合量子密钥分发(QKD)实验方案
java·jvm·spring boot·后端·spring·spring cloud·maven
喜欢敲代码的程序员13 小时前
SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:日志管理(四)集成Spring Security
spring boot·mysql·spring·vue·mybatis
Resean022313 小时前
SpringMVC 6+源码分析(二)DispatcherServlet实例化流程 1
java·spring boot·spring·servlet·springmvc
sophie旭14 小时前
《深入浅出react》总结之 10.7 scheduler 异步调度原理
前端·react.js·源码
hqxstudying18 小时前
SpringBoot相关注解
java·spring boot·后端
江湖中的阿龙1 天前
SpringBoot:基于 Redis 自定义注解实现后端接口防重复提交校验(幂等操作)
spring boot·redis·后端·幂等操作
孟婆来包棒棒糖~1 天前
Docker快速入门
运维·spring boot·docker·容器·tomcat
hqxstudying1 天前
SpringBoot启动项目详解
java·spring boot·后端