Java中的@PreAuthorize注解源码解析
作者:Sterne_
一、PrePostAdviceReactiveMethodInterceptor类
作用
拦截@PreAuthorize注解标记的方法。
源码分析
// 源码存在删减 public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor { private Authentication anonymous = new AnonymousAuthenticationToken("key", "anonymous", private final MethodSecurityMetadataSource attributeSource; private final PreInvocationAuthorizationAdvice preInvocationAdvice; private final PostInvocationAuthorizationAdvice postAdvice; public PrePostAdviceReactiveMethodInterceptor(MethodSecurityMetadataSource attributeSource, PreInvocationAuthorizationAdvice preInvocationAdvice, PostInvocationAuthorizationAdvice postInvocationAdvice) { // attributeSource->PrePostAnnotationSecurityMetadataSource类,下文有相关解析 this.attributeSource = attributeSource; // preInvocationAdvice->ExpressionBasedPreInvocationAdvice类,下文有相关解析 this.preInvocationAdvice = preInvocationAdvice; this.postAdvice = postInvocationAdvice; } @Override public Object invoke(final MethodInvocation invocation) { Method method = invocation.getMethod(); Class<?> returnType = method.getReturnType(); Class<?> targetClass = invocation.getThis().getClass(); // 关键步骤1,获取当前方法的安全属性集合,该方法解析在目录标题二 Collection<ConfigAttribute> attributes = this.attributeSource.getAttributes(method, targetClass); // 关键步骤2:获取@PreAuthorize注解的value值 PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); Mono<Authentication> toInvoke = ReactiveSecurityContextHolder.getContext() // Mono<SecurityContext> .map(SecurityContext::getAuthentication)// Mono<Authentication> .defaultIfEmpty(this.anonymous) // 关键步骤3:调用ExpressionBasedPreInvocationAdvice类中的before方法,filter结果为true则保留元素,为false则删除元素 .filter((auth) -> this.preInvocationAdvice.before(auth, invocation, preAttr)) .switchIfEmpty(Mono.defer(() -> Mono.error(new AccessDeniedException("Denied")))); }
对关键步骤3进行补充说明:
- 当前的安全上下文中不存在认证信息(Authentication),即 ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication) 返回空的 Mono 对象。
- 调用 preInvocationAdvice.before(auth, invocation, preAttr) 方法返回 false,即预授权逻辑拒绝了访问请求。
- 在这两种情况下,都会使用 Mono.error(new AccessDeniedException(“Denied”)) 创建一个错误的 Mono 对象,并通过 switchIfEmpty 方法替换之前的空 Mono 对象,从而触发异常并抛出 AccessDeniedException。
二、PrePostAnnotationSecurityMetadataSource类
类的继承关系
作用
- 解析注解:它解析方法上的PreAuthorize和PostAuthorize等注解,提取其中的权限表达式、角色信息等。
- 提供权限验证元数据:根据解析得到的注解信息,PrePostAnnotationSecurityMetadataSource提供相应的权限验证元数据。这些元数据通常是ConfigAttribute对象的集合,每个ConfigAttribute表示一个权限验证的配置。
- 支持方法级别的权限验证:通过为方法提供权限验证元数据,PrePostAnnotationSecurityMetadataSource支持在方法级别对权限进行验证。这使得开发者可以在方法执行前后定义细粒度的权限控制逻辑。
- 与其他组件配合使用:PrePostAnnotationSecurityMetadataSource通常与其他Spring Security的组件(如AccessDecisionManager、MethodSecurityInterceptor等)配合使用,以实现方法级别的权限验证。
源码分析
// 获取@PreAuthorize相关源码部分展示 public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { private final PrePostInvocationAttributeFactory attributeFactory; public PrePostAnnotationSecurityMetadataSource(PrePostInvocationAttributeFactory attributeFactory) { this.attributeFactory = attributeFactory; } // PrePostAdviceReactiveMethodInterceptor invoke方法中调用该方法获取attributes @Override public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) { if (method.getDeclaringClass() == Object.class) { return Collections.emptyList(); } PreAuthorize preAuthorize = findAnnotation(method, targetClass, PreAuthorize.class); if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null) { // There is no meta-data so return return Collections.emptyList(); } String filterObject = (preFilter != null) ? preFilter.filterTarget() : null; // 获取@PreAuthorize注解的表达式 String preAuthorizeAttribute = (preAuthorize != null) ? preAuthorize.value() : null; ArrayList<ConfigAttribute> attrs = new ArrayList<>(2); // 关键步骤1:创建PreAuthorize对应的ConfigAttribute PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(preFilterAttribute, filterObject, preAuthorizeAttribute); if (pre != null) { attrs.add(pre); } // 将容器的容量调整为当前元素的数量 attrs.trimToSize(); return attrs; } }
// 解析注解中的表达式,创建相应的注解属性对象 public class ExpressionBasedAnnotationAttributeFactory implements PrePostInvocationAttributeFactory { private final Object parserLock = new Object(); private ExpressionParser parser; // 对应下方代码的DefaultMethodSecurityExpressionHandler private MethodSecurityExpressionHandler handler; public ExpressionBasedAnnotationAttributeFactory(MethodSecurityExpressionHandler handler) { this.handler = handler; } // param: preAuthorizeAttribute 获取到的@PreAuthorize注解的表达式 @Override public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute, String filterObject, String preAuthorizeAttribute) { try { // SpEL表达式解析器 ExpressionParser parser = getParser(); // 关键步骤 Expression preAuthorizeExpression = (preAuthorizeAttribute != null) ? parser.parseExpression(preAuthorizeAttribute) : parser.parseExpression("permitAll"); Expression preFilterExpression = (preFilterAttribute != null) ? parser.parseExpression(preFilterAttribute) : null; // 关键步骤 return new PreInvocationExpressionAttribute(preFilterExpression, filterObject, preAuthorizeExpression); } catch (ParseException ex) { throw new IllegalArgumentException("Failed to parse expression '" + ex.getExpressionString() + "'", ex); } } }
三、ExpressionBasedPreInvocationAdvice类
作用
解析@PreAuthorize中的SpEL表达式
源码分析
// 源码存在部分删减,仅展示分析与@PreAuthorize相关的内容 public class ExpressionBasedPreInvocationAdvice implements PreInvocationAuthorizationAdvice { // 关键类 第四点有对该类的关键方法进行解析 private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); @Override public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) { PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr; // 关键步骤 创建SpEL解析上下文 EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); Expression preAuthorize = preAttr.getAuthorizeExpression(); // 关键步骤 计算表达式值 return (preAuthorize != null) ? ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true; } }
ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx方法补充说明:
根据提供的安全表达式和评估上下文 ctx 来评估安全表达式的结果,并返回一个布尔值。true,则权限校验通过;false,则校验失败。
四、DefaultMethodSecurityExpressionHandler类
作用
- 创建评估上下文:在安全表达式求值之前,DefaultMethodSecurityExpressionHandler 会创建一个评估上下文EvaluationContext对象,以提供给安全表达式进行求值。评估上下文包含了当前用户的身份验证信息、目标对象和方法参数等相关信息。
- 权限注解的处理:DefaultMethodSecurityExpressionHandler 支持处理方法参数上的权限注解,例如 @PreFilter 和 @PostFilter 注解。它会将这些注解解析为相应的安全表达式,并在评估上下文中传递方法参数的信息,以进行权限过滤操作。
源码分析
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation> implements MethodSecurityExpressionHandler { // 用于处理表达式中的bean对象获取 private BeanResolver beanResolver; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); // 这个类非常重要,下文会对这个类单独进行解析 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer(); private PermissionCacheOptimizer permissionCacheOptimizer = null; private String defaultRolePrefix = "ROLE_"; public DefaultMethodSecurityExpressionHandler() { } /** * ExpressionBasedPreInvocationAdvice的before方法中调用该方法,创建方法安全表达式的评估上下文 */ @Override public final EvaluationContext createEvaluationContext(Authentication authentication, T invocation) { SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation); StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation); ctx.setBeanResolver(this.beanResolver); ctx.setRootObject(root); return ctx; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.beanResolver = new BeanFactoryResolver(applicationContext); } /** * 在 Spring Security 中,安全表达式用于在方法级别进行访问控制的决策。createEvaluationContextInternal方法在方法级别的安全表达式求值过程中被调用,其主要作用是创建一个评估上下文对象,以提供给安全表达式进行求值。 */ @Override public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) { return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer()); } /** * 方法级别的安全表达式通常需要访问当前用户、目标对象和方法参数等相关 信息。createEvaluationContextInternal方法会使用 MethodSecurityExpressionRoot类的实例作为权限表达式的根对象,以便在表达式中访问这些信息。 */ @Override protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication); root.setThis(invocation.getThis()); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(getTrustResolver()); root.setRoleHierarchy(getRoleHierarchy()); root.setDefaultRolePrefix(getDefaultRolePrefix()); return root; } }
对 MethodSecurityExpressionOperations 类进行补充说明:
MethodSecurityExpressionOperations 接口定义了一组方法,用于在安全表达式中进行常见的操作和判断,例如获取当前用户信息、检查角色和权限等。下面举例该类的部分方法:
- boolean hasAuthority(String authority)
- boolean hasAnyAuthority(String… authorities)
- boolean hasRole(String role)
- boolean hasAnyRole(String… roles)
- boolean permitAll()
- boolean denyAll()
- boolean hasPermission(Object target, Object permission)
对 DefaultSecurityParameterNameDiscoverer 类进行补充说明: 在 Spring Security 中,当使用方法级别的注解(如 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter)时,需要引用方法参数的名称来进行安全性评估和过滤操作。但编译器默认情况下不会在编译过程中保留方法参数的名称,而是使用类似 “arg0”、“arg1” 等默认名称。DefaultSecurityParameterNameDiscoverer 的作用就是解决这个问题,它通过不同的策略来发现方法参数的名称,以便在安全性注解中引用正确的参数。
到此这篇关于Java中的@PreAuthorize注解源码解析的文章就介绍到这了,更多相关@PreAuthorize注解源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!