theme: channing-cyan
highlight: atom-one-dark-reasonable
1、SpringWebMvcImportSelector
- SpringSecurity支持在SpringMVC进行参数解析的时候填充参数,支持以下的对象
- 通过@AuthenticationPrincipal,获取UserDetails
- 通过@CurrentSecurityContext,获取SecurityContext
- 通过参数类型为CsrfToken获取CsrfToken
- 究其原因是因为SpringSecurity为这些参数类型注册了对应的参数解析器
- SpringWebMvcImportSelector源码如下:
class SpringWebMvcImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean webmvcPresent = ClassUtils.isPresent(
"org.springframework.web.servlet.DispatcherServlet",
getClass().getClassLoader());
return webmvcPresent
? new String[] {
"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" }
: new String[] {};
}
}
- 分析可以看出当可以加载SpringMVC的DispatcherServlet的时候注册一个WebMvcSecurityConfiguration类
2、WebMvcSecurityConfiguration
- 此类作用如下:
- 注册四个参数解析器
- AuthenticationPrincipalArgumentResolver:针对@AuthenticationPrincipal,注意这里是两个名称相同并且支持的注解名称也一模一样的
- CurrentSecurityContextArgumentResolver:针对@CurrentSecurityContext
- CsrfTokenArgumentResolver:针对CsrfToken
- 注册CsrfRequestDataValueProcessor:当开启了Csrf的情况下,此类负责将Csrf添加到具有隐藏域的表单中
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);
argumentResolvers.add(new CsrfTokenArgumentResolver());
}
@Bean
RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
}
}
2.1 AuthenticationPrincipalArgumentResolver
- 这里仅介绍org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver,另外一个多了SpEL的解析方式
- 可以看出当方法入参中有携带@AuthenticationPrincipal的时候,会从线程级别的安全上下文中获取认证对象
@Deprecated
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();
if (principal != null && !parameter.getParameterType().isAssignableFrom(principal.getClass())) {
AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
if (authPrincipal.errorOnInvalidType()) {
throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
}
return null;
}
return principal;
}
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
T annotation = parameter.getParameterAnnotation(annotationClass);
if (annotation != null) {
return annotation;
}
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
for (Annotation toSearch : annotationsToSearch) {
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
if (annotation != null) {
return annotation;
}
}
return null;
}
}
- 这里的认证对象指的是Authentication,部分实现如下:
- UsernamePasswordAuthenticationToken:通过用户名和密码生成的认证对象
- RememberMeAuthenticationToken:通过记住我令牌生成的认证对象
- …
2.2 CurrentSecurityContextArgumentResolver
- 支持解析标注了@CurrentSecurityContext注解的参数、
- 支持Controller方法中的入参中有标注了@CurrentSecurityContext放在SecurityContext参数上
- 支持 Spring SpEl表达式从SecurityContext中获取值
- eg:@CurrentSecurityContext(expression=“authentication”) Authentication authentication
public final class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
private ExpressionParser parser = new SpelExpressionParser();
private BeanResolver beanResolver;
@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 annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
String expressionToParse = annotation.expression();
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;
}
public void setBeanResolver(BeanResolver beanResolver) {
Assert.notNull(beanResolver, "beanResolver cannot be null");
this.beanResolver = beanResolver;
}
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
T annotation = parameter.getParameterAnnotation(annotationClass);
if (annotation != null) {
return annotation;
}
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
for (Annotation toSearch : annotationsToSearch) {
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
if (annotation != null) {
return annotation;
}
}
return null;
}
}
2.3 CsrfTokenArgumentResolver
- 源码很简单就是直接从请求域中获得CsrfToken
public final class CsrfTokenArgumentResolver implements HandlerMethodArgumentResolver {
@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 token = (CsrfToken) webRequest.getAttribute(CsrfToken.class.getName(),
RequestAttributes.SCOPE_REQUEST);
return token;
}
}
- 至于为什么在请求域中有CsrfToken,下面的代码能看出是在SpringSecurity的CsrfFilter中负责将CsrfToken放到请求域中的
public final class CsrfFilter extends OncePerRequestFilter {
......
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
.......
request.setAttribute(CsrfToken.class.getName(), csrfToken);
......
}
.......
}
- 又衍生出一个问题,到底是setAttribute还是getAttribute先执行呢
- 下图能够看出应用程序中一共有五个过滤器,前三个是SpringMVC提供的,第四个就是FilterChainProxy也就是SpringSecurity的过滤器链,CsrfFilter就是在这里面执行的,而参数解析器是在DispatcherServlet中负责执行的,而DispatcherServlet最终是这里的第五个过滤器中负责调用的
- 所以说一定是先是setAttribute还再getAttribute
![在这里插入图片描述](https://img-blog.csdnimg.cn/3cf77bb1106b4111b8b9188f14ac123d.png#pic_center)