Java中的@PreAuthorize注解源碼解析
一、PrePostAdviceReactiveMethodInterceptor類
作用
攔截@PreAuthorize注解標(biāo)記的方法。
源碼分析
// 源碼存在刪減
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類,下文有相關(guān)解析
this.attributeSource = attributeSource;
// preInvocationAdvice->ExpressionBasedPreInvocationAdvice類,下文有相關(guān)解析
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();
// 關(guān)鍵步驟1,獲取當(dāng)前方法的安全屬性集合,該方法解析在目錄標(biāo)題二
Collection<ConfigAttribute> attributes = this.attributeSource.getAttributes(method, targetClass);
// 關(guān)鍵步驟2:獲取@PreAuthorize注解的value值
PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
Mono<Authentication> toInvoke = ReactiveSecurityContextHolder.getContext() // Mono<SecurityContext>
.map(SecurityContext::getAuthentication)// Mono<Authentication>
.defaultIfEmpty(this.anonymous)
// 關(guān)鍵步驟3:調(diào)用ExpressionBasedPreInvocationAdvice類中的before方法,filter結(jié)果為true則保留元素,為false則刪除元素
.filter((auth) -> this.preInvocationAdvice.before(auth, invocation, preAttr))
.switchIfEmpty(Mono.defer(() -> Mono.error(new AccessDeniedException("Denied"))));
}對(duì)關(guān)鍵步驟3進(jìn)行補(bǔ)充說(shuō)明:
- 當(dāng)前的安全上下文中不存在認(rèn)證信息(Authentication),即 ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication) 返回空的 Mono 對(duì)象。
- 調(diào)用 preInvocationAdvice.before(auth, invocation, preAttr) 方法返回 false,即預(yù)授權(quán)邏輯拒絕了訪問(wèn)請(qǐng)求。
- 在這兩種情況下,都會(huì)使用 Mono.error(new AccessDeniedException(“Denied”)) 創(chuàng)建一個(gè)錯(cuò)誤的 Mono 對(duì)象,并通過(guò) switchIfEmpty 方法替換之前的空 Mono 對(duì)象,從而觸發(fā)異常并拋出 AccessDeniedException。
二、PrePostAnnotationSecurityMetadataSource類
類的繼承關(guān)系

作用
- 解析注解:它解析方法上的PreAuthorize和PostAuthorize等注解,提取其中的權(quán)限表達(dá)式、角色信息等。
- 提供權(quán)限驗(yàn)證元數(shù)據(jù):根據(jù)解析得到的注解信息,PrePostAnnotationSecurityMetadataSource提供相應(yīng)的權(quán)限驗(yàn)證元數(shù)據(jù)。這些元數(shù)據(jù)通常是ConfigAttribute對(duì)象的集合,每個(gè)ConfigAttribute表示一個(gè)權(quán)限驗(yàn)證的配置。
- 支持方法級(jí)別的權(quán)限驗(yàn)證:通過(guò)為方法提供權(quán)限驗(yàn)證元數(shù)據(jù),PrePostAnnotationSecurityMetadataSource支持在方法級(jí)別對(duì)權(quán)限進(jìn)行驗(yàn)證。這使得開(kāi)發(fā)者可以在方法執(zhí)行前后定義細(xì)粒度的權(quán)限控制邏輯。
- 與其他組件配合使用:PrePostAnnotationSecurityMetadataSource通常與其他Spring Security的組件(如AccessDecisionManager、MethodSecurityInterceptor等)配合使用,以實(shí)現(xiàn)方法級(jí)別的權(quán)限驗(yàn)證。
源碼分析
// 獲取@PreAuthorize相關(guān)源碼部分展示
public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecurityMetadataSource {
private final PrePostInvocationAttributeFactory attributeFactory;
public PrePostAnnotationSecurityMetadataSource(PrePostInvocationAttributeFactory attributeFactory) {
this.attributeFactory = attributeFactory;
}
// PrePostAdviceReactiveMethodInterceptor invoke方法中調(diào)用該方法獲取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注解的表達(dá)式
String preAuthorizeAttribute = (preAuthorize != null) ? preAuthorize.value() : null;
ArrayList<ConfigAttribute> attrs = new ArrayList<>(2);
// 關(guān)鍵步驟1:創(chuàng)建PreAuthorize對(duì)應(yīng)的ConfigAttribute
PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(preFilterAttribute,
filterObject, preAuthorizeAttribute);
if (pre != null) {
attrs.add(pre);
}
// 將容器的容量調(diào)整為當(dāng)前元素的數(shù)量
attrs.trimToSize();
return attrs;
}
}// 解析注解中的表達(dá)式,創(chuàng)建相應(yīng)的注解屬性對(duì)象
public class ExpressionBasedAnnotationAttributeFactory implements PrePostInvocationAttributeFactory {
private final Object parserLock = new Object();
private ExpressionParser parser;
// 對(duì)應(yīng)下方代碼的DefaultMethodSecurityExpressionHandler
private MethodSecurityExpressionHandler handler;
public ExpressionBasedAnnotationAttributeFactory(MethodSecurityExpressionHandler handler) {
this.handler = handler;
}
// param: preAuthorizeAttribute 獲取到的@PreAuthorize注解的表達(dá)式
@Override
public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute, String filterObject,
String preAuthorizeAttribute) {
try {
// SpEL表達(dá)式解析器
ExpressionParser parser = getParser();
// 關(guān)鍵步驟
Expression preAuthorizeExpression = (preAuthorizeAttribute != null)
? parser.parseExpression(preAuthorizeAttribute) : parser.parseExpression("permitAll");
Expression preFilterExpression = (preFilterAttribute != null) ? parser.parseExpression(preFilterAttribute)
: null;
// 關(guān)鍵步驟
return new
PreInvocationExpressionAttribute(preFilterExpression, filterObject, preAuthorizeExpression);
}
catch (ParseException ex) {
throw new IllegalArgumentException("Failed to parse expression '" + ex.getExpressionString() + "'", ex);
}
}
}三、ExpressionBasedPreInvocationAdvice類
作用
解析@PreAuthorize中的SpEL表達(dá)式
源碼分析
// 源碼存在部分刪減,僅展示分析與@PreAuthorize相關(guān)的內(nèi)容
public class ExpressionBasedPreInvocationAdvice implements PreInvocationAuthorizationAdvice {
// 關(guān)鍵類 第四點(diǎn)有對(duì)該類的關(guān)鍵方法進(jìn)行解析
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
@Override
public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) {
PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr;
// 關(guān)鍵步驟 創(chuàng)建SpEL解析上下文
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
Expression preAuthorize = preAttr.getAuthorizeExpression();
// 關(guān)鍵步驟 計(jì)算表達(dá)式值
return (preAuthorize != null) ?
ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true;
}
}ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx方法補(bǔ)充說(shuō)明:
根據(jù)提供的安全表達(dá)式和評(píng)估上下文 ctx 來(lái)評(píng)估安全表達(dá)式的結(jié)果,并返回一個(gè)布爾值。true,則權(quán)限校驗(yàn)通過(guò);false,則校驗(yàn)失敗。
四、DefaultMethodSecurityExpressionHandler類
作用
- 創(chuàng)建評(píng)估上下文:在安全表達(dá)式求值之前,DefaultMethodSecurityExpressionHandler 會(huì)創(chuàng)建一個(gè)評(píng)估上下文EvaluationContext對(duì)象,以提供給安全表達(dá)式進(jìn)行求值。評(píng)估上下文包含了當(dāng)前用戶的身份驗(yàn)證信息、目標(biāo)對(duì)象和方法參數(shù)等相關(guān)信息。
- 權(quán)限注解的處理:DefaultMethodSecurityExpressionHandler 支持處理方法參數(shù)上的權(quán)限注解,例如 @PreFilter 和 @PostFilter 注解。它會(huì)將這些注解解析為相應(yīng)的安全表達(dá)式,并在評(píng)估上下文中傳遞方法參數(shù)的信息,以進(jìn)行權(quán)限過(guò)濾操作。
源碼分析
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
implements MethodSecurityExpressionHandler {
// 用于處理表達(dá)式中的bean對(duì)象獲取
private BeanResolver beanResolver;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
// 這個(gè)類非常重要,下文會(huì)對(duì)這個(gè)類單獨(dú)進(jìn)行解析
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer();
private PermissionCacheOptimizer permissionCacheOptimizer = null;
private String defaultRolePrefix = "ROLE_";
public DefaultMethodSecurityExpressionHandler() {
}
/**
* ExpressionBasedPreInvocationAdvice的before方法中調(diào)用該方法,創(chuàng)建方法安全表達(dá)式的評(píng)估上下文
*/
@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 中,安全表達(dá)式用于在方法級(jí)別進(jìn)行訪問(wèn)控制的決策。createEvaluationContextInternal方法在方法級(jí)別的安全表達(dá)式求值過(guò)程中被調(diào)用,其主要作用是創(chuàng)建一個(gè)評(píng)估上下文對(duì)象,以提供給安全表達(dá)式進(jìn)行求值。
*/
@Override
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
}
/**
* 方法級(jí)別的安全表達(dá)式通常需要訪問(wèn)當(dāng)前用戶、目標(biāo)對(duì)象和方法參數(shù)等相關(guān) 信息。createEvaluationContextInternal方法會(huì)使用 MethodSecurityExpressionRoot類的實(shí)例作為權(quán)限表達(dá)式的根對(duì)象,以便在表達(dá)式中訪問(wèn)這些信息。
*/
@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;
}
}對(duì) MethodSecurityExpressionOperations 類進(jìn)行補(bǔ)充說(shuō)明:
MethodSecurityExpressionOperations 接口定義了一組方法,用于在安全表達(dá)式中進(jìn)行常見(jiàn)的操作和判斷,例如獲取當(dāng)前用戶信息、檢查角色和權(quán)限等。下面舉例該類的部分方法:
- 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)
對(duì) DefaultSecurityParameterNameDiscoverer 類進(jìn)行補(bǔ)充說(shuō)明: 在 Spring Security 中,當(dāng)使用方法級(jí)別的注解(如 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter)時(shí),需要引用方法參數(shù)的名稱來(lái)進(jìn)行安全性評(píng)估和過(guò)濾操作。但編譯器默認(rèn)情況下不會(huì)在編譯過(guò)程中保留方法參數(shù)的名稱,而是使用類似 “arg0”、“arg1” 等默認(rèn)名稱。DefaultSecurityParameterNameDiscoverer 的作用就是解決這個(gè)問(wèn)題,它通過(guò)不同的策略來(lái)發(fā)現(xiàn)方法參數(shù)的名稱,以便在安全性注解中引用正確的參數(shù)。
到此這篇關(guān)于Java中的@PreAuthorize注解源碼解析的文章就介紹到這了,更多相關(guān)@PreAuthorize注解源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring+Quartz實(shí)現(xiàn)動(dòng)態(tài)任務(wù)調(diào)度詳解
這篇文章主要介紹了Spring+Quartz實(shí)現(xiàn)動(dòng)態(tài)任務(wù)調(diào)度詳解,最近經(jīng)?;趕pring?boot寫定時(shí)任務(wù),并且是使用注解的方式進(jìn)行實(shí)現(xiàn),分成的方便將自己的類注入spring容器,需要的朋友可以參考下2024-01-01
詳解java集成支付寶支付接口(JSP+支付寶20160912)
本篇文章主要介紹了java集成支付寶支付接口,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
Java中Retry方法的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了Java中Retry方法的簡(jiǎn)單實(shí)現(xiàn),Retry主要是利用Java的lambda表達(dá)式和線程接口實(shí)現(xiàn)有返回值和無(wú)返回值的重試,思考了下就寫了一個(gè)簡(jiǎn)易R(shí)etry功能分享出來(lái),需要的朋友可以參考下2024-01-01
IDEA JarEditor編輯jar包方式(直接新增,修改,刪除jar包內(nèi)的class文件)
文章主要介紹了如何使用IDEA的JarEditor插件直接修改jar包內(nèi)的class文件,而不需要手動(dòng)解壓、反編譯和重新打包,通過(guò)該插件,可以更方便地進(jìn)行jar包的修改和測(cè)試2025-01-01
超詳細(xì)講解Java秒殺項(xiàng)目用戶驗(yàn)證模塊的實(shí)現(xiàn)
這是一個(gè)主要使用java開(kāi)發(fā)的秒殺系統(tǒng),項(xiàng)目比較大,所以本篇只實(shí)現(xiàn)了用戶驗(yàn)證模塊,代碼非常詳盡,感興趣的朋友快來(lái)看看2022-03-03
Java多線程文件分片下載實(shí)現(xiàn)的示例代碼
這篇文章主要介紹了Java多線程文件分片下載實(shí)現(xiàn)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Spring裝配Bean之用Java代碼安裝配置bean詳解
這篇文章主要給大家介紹了關(guān)于Spring裝配Bean之用Java代碼安裝配置bean的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
Java中JSONObject和Map<String,?Object>的轉(zhuǎn)換方法
平時(shí)對(duì)接口時(shí),經(jīng)常遇到j(luò)son字符串和map對(duì)象之間的交互,這篇文章主要給大家介紹了關(guān)于Java中JSONObject和Map<String,?Object>的轉(zhuǎn)換方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07

