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

相关文章

  • springboot layui hutool Excel导入的实现

    springboot layui hutool Excel导入的实现

    本文主要介绍了springboot layui hutool Excel导入的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-03-03
  • 基于SpringBoot的Docker部署实践

    基于SpringBoot的Docker部署实践

    在云计算和微服务架构日益普及的今天,Docker已成为一种主流的应用部署方式,本文将详细介绍如何将基于Spring Boot的项目部署到Docker容器中,需要的朋友可以参考下
    2023-07-07
  • SpringBoot整合jasypt实现敏感信息的加密详解

    SpringBoot整合jasypt实现敏感信息的加密详解

    一般公司的核心业务代码中,都会存在与数据库、第三方通信的secret key等敏感信息,如果以明文的方式存储,一旦泄露,那将会给公司带来巨大的损失。本篇文章通过讲解:Springboot集成Jasypt对项目敏感信息进行加密,提高系统的安全性
    2022-09-09
  • springboot 之jpa高级查询操作

    springboot 之jpa高级查询操作

    这篇文章主要介绍了springboot 之jpa高级查询操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 一文解开java中字符串编码的小秘密(干货)

    一文解开java中字符串编码的小秘密(干货)

    这篇文章主要介绍了一文解开java中字符串编码的小秘密(干货),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Spring boot随机端口你都不会还怎么动态扩容

    Spring boot随机端口你都不会还怎么动态扩容

    这篇文章主要介绍了Spring boot随机端口你都不会还怎么动态扩容,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • java正则表达式匹配规则超详细总结

    java正则表达式匹配规则超详细总结

    正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别,下面这篇文章主要给大家介绍了关于java正则表达式匹配规则的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • Java新API的时间格式化

    Java新API的时间格式化

    这篇文章主要介绍了Java新API的时间格式化,新的时间API的时间格式化由java.time.format.DateTimeFormatter负责,更多相关资料需要的小伙伴可以参考一下
    2022-05-05
  • Java面向对象程序设计:抽象类,接口用法实例分析

    Java面向对象程序设计:抽象类,接口用法实例分析

    这篇文章主要介绍了Java面向对象程序设计:抽象类,接口用法,结合实例形式分析了java抽象类与接口相关概念、原理、用法与操作注意事项,需要的朋友可以参考下
    2020-04-04
  • Java8如何从一个list中获取某一元素集合

    Java8如何从一个list中获取某一元素集合

    这篇文章主要介绍了Java8如何从一个list中获取某一元素集合,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07

最新评论