Java中的@PreAuthorize注解源码解析

 更新时间:2023年10月07日 08:39:34   作者:Sterne_  
这篇文章主要介绍了Java中的@PreAuthorize注解源码解析,@PreAuthorize注解会在方法执行前进行权限验证,支持Spring EL表达式,它是基于方法注解的权限解决方案,需要的朋友可以参考下

一、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类

类的继承关系

PrePostAnnotationSecuirtyMetadataSource继承实现类

作用

  • 解析注解:它解析方法上的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注解源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java与C++实现相同的MD5加密算法简单实例

    Java与C++实现相同的MD5加密算法简单实例

    下面小编就为大家带来一篇Java与C++实现相同的MD5加密算法简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • Springboot整合nacos报错无法连接nacos的解决

    Springboot整合nacos报错无法连接nacos的解决

    这篇文章主要介绍了Springboot整合nacos报错无法连接nacos的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Java中线程Thread的特点及使用

    Java中线程Thread的特点及使用

    这篇文章主要介绍了Java中线程的特点及使用,线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程,那么线程该如何使用,让我们一起来看看吧
    2023-04-04
  • Spring Boot 中该如何防御计时攻击

    Spring Boot 中该如何防御计时攻击

    这篇文章主要介绍了Spring Boot 中该如何防御计时攻击,帮助大家更好的使用spring boot框架,感兴趣的朋友可以了解下
    2020-09-09
  • 详解如何用Java去除HTML标签

    详解如何用Java去除HTML标签

    在平时工作中,偶尔会用 Java 做一些解析HTML的工作。有时需要删除所有的HTML标签,只保留纯文字内容。这个问题在做过一些爬虫工作的朋友来说很简单。下面来说说,我们平时使用到的集中解析的方法
    2022-12-12
  • WebService的相关概念

    WebService的相关概念

    这篇文章主要介绍了WebService的相关概念的相关资料,需要的朋友可以参考下
    2017-10-10
  • Java Ribbon负载均衡详细讲解

    Java Ribbon负载均衡详细讲解

    Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,这篇文章主要介绍了Ribbon负载均衡服务调用案例代码,需要的朋友可以参考下
    2023-01-01
  • springmvc实现文件上传功能

    springmvc实现文件上传功能

    这篇文章主要为大家详细介绍了springmvc实现文件上传功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • Idea自定义方法注释模板的教程详解(去param括号、return全类名)

    Idea自定义方法注释模板的教程详解(去param括号、return全类名)

    这篇文章主要介绍了Idea自定义方法注释模板(去param括号、return全类名),本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • SpringBoot2.0解决Long型数据转换成json格式时丢失精度问题

    SpringBoot2.0解决Long型数据转换成json格式时丢失精度问题

    这篇文章主要介绍了SpringBoot2.0解决Long型数据转换成json格式时丢失精度问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06

最新评论