[SpringSecurity5.6.2源码分析三]:SpringWebMvcImportSelector
原创
©著作权归作者所有:来自51CTO博客作者wx5bb9ee7a9253c的原创作品,请联系作者获取转载授权,否则将追究法律责任
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);
// 注册 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());
}
}
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注解的参数、
- • eg:@CurrentSecurityContext(expression="authentication") Authentication authentication
- • 支持Controller方法中的入参中有标注了@CurrentSecurityContext放在SecurityContext参数上
- • 支持 Spring SpEl表达式从SecurityContext中获取值
public final class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
private ExpressionParser parser = new SpelExpressionParser();
private BeanResolver beanResolver;
/**
* 此参数解析器只能支持带有 {@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 进行解析
// 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;
}
/**
* Set the {@link BeanResolver} to be used on the expressions
* @param beanResolver the {@link BeanResolver} to use
*/
public void setBeanResolver(BeanResolver beanResolver) {
Assert.notNull(beanResolver, "beanResolver cannot be null");
this.beanResolver = beanResolver;
}
/**
* 在指定的方法参数上,获得指定的注解
* @param annotationClass the class of the {@link Annotation} to find on the
* {@link MethodParameter}
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
* @return the {@link Annotation} that was found or null.
*/
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 {
/**
* 此参数解析器仅支持 {@code CsrfToken}
* @param parameter
* @return
*/
@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;
}
}
- • 至于为什么在请求域中有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