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"))));
}對關(guān)鍵步驟3進(jìn)行補(bǔ)充說明:
- 當(dāng)前的安全上下文中不存在認(rèn)證信息(Authentication),即 ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication) 返回空的 Mono 對象。
- 調(diào)用 preInvocationAdvice.before(auth, invocation, preAttr) 方法返回 false,即預(yù)授權(quán)邏輯拒絕了訪問請求。
- 在這兩種情況下,都會使用 Mono.error(new AccessDeniedException(“Denied”)) 創(chuàng)建一個錯誤的 Mono 對象,并通過 switchIfEmpty 方法替換之前的空 Mono 對象,從而觸發(fā)異常并拋出 AccessDeniedException。
二、PrePostAnnotationSecurityMetadataSource類
類的繼承關(guān)系

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

