Java中的@PreAuthorize注解源碼解析
一、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”)) 創(chuàng)建一個錯誤的 Mono 對象,并通過 switchIfEmpty 方法替換之前的空 Mono 對象,從而觸發(fā)異常并拋出 AccessDeniedException。
二、PrePostAnnotationSecurityMetadataSource類
類的繼承關系

作用
- 解析注解:它解析方法上的PreAuthorize和PostAuthorize等注解,提取其中的權限表達式、角色信息等。
- 提供權限驗證元數(shù)據(jù):根據(jù)解析得到的注解信息,PrePostAnnotationSecurityMetadataSource提供相應的權限驗證元數(shù)據(jù)。這些元數(shù)據(jù)通常是ConfigAttribute對象的集合,每個ConfigAttribute表示一個權限驗證的配置。
- 支持方法級別的權限驗證:通過為方法提供權限驗證元數(shù)據(jù),PrePostAnnotationSecurityMetadataSource支持在方法級別對權限進行驗證。這使得開發(fā)者可以在方法執(zhí)行前后定義細粒度的權限控制邏輯。
- 與其他組件配合使用:PrePostAnnotationSecurityMetadataSource通常與其他Spring Security的組件(如AccessDecisionManager、MethodSecurityInterceptor等)配合使用,以實現(xiàn)方法級別的權限驗證。
源碼分析
// 獲取@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:創(chuàng)建PreAuthorize對應的ConfigAttribute
PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(preFilterAttribute,
filterObject, preAuthorizeAttribute);
if (pre != null) {
attrs.add(pre);
}
// 將容器的容量調整為當前元素的數(shù)量
attrs.trimToSize();
return attrs;
}
}// 解析注解中的表達式,創(chuàng)建相應的注解屬性對象
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;
// 關鍵步驟 創(chuàng)建SpEL解析上下文
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
Expression preAuthorize = preAttr.getAuthorizeExpression();
// 關鍵步驟 計算表達式值
return (preAuthorize != null) ?
ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true;
}
}ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx方法補充說明:
根據(jù)提供的安全表達式和評估上下文 ctx 來評估安全表達式的結果,并返回一個布爾值。true,則權限校驗通過;false,則校驗失敗。
四、DefaultMethodSecurityExpressionHandler類
作用
- 創(chuàng)建評估上下文:在安全表達式求值之前,DefaultMethodSecurityExpressionHandler 會創(chuàng)建一個評估上下文EvaluationContext對象,以提供給安全表達式進行求值。評估上下文包含了當前用戶的身份驗證信息、目標對象和方法參數(shù)等相關信息。
- 權限注解的處理:DefaultMethodSecurityExpressionHandler 支持處理方法參數(shù)上的權限注解,例如 @PreFilter 和 @PostFilter 注解。它會將這些注解解析為相應的安全表達式,并在評估上下文中傳遞方法參數(shù)的信息,以進行權限過濾操作。
源碼分析
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方法中調用該方法,創(chuà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 中,安全表達式用于在方法級別進行訪問控制的決策。createEvaluationContextInternal方法在方法級別的安全表達式求值過程中被調用,其主要作用是創(chuàng)建一個評估上下文對象,以提供給安全表達式進行求值。
*/
@Override
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
}
/**
* 方法級別的安全表達式通常需要訪問當前用戶、目標對象和方法參數(shù)等相關 信息。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)時,需要引用方法參數(shù)的名稱來進行安全性評估和過濾操作。但編譯器默認情況下不會在編譯過程中保留方法參數(shù)的名稱,而是使用類似 “arg0”、“arg1” 等默認名稱。DefaultSecurityParameterNameDiscoverer 的作用就是解決這個問題,它通過不同的策略來發(fā)現(xiàn)方法參數(shù)的名稱,以便在安全性注解中引用正確的參數(shù)。
到此這篇關于Java中的@PreAuthorize注解源碼解析的文章就介紹到這了,更多相關@PreAuthorize注解源碼內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java 利用反射機制,獲取實體所有屬性和方法,并對屬性賦值
這篇文章主要介紹了 java 利用反射機制,獲取實體所有屬性和方法,并對屬性賦值的相關資料,需要的朋友可以參考下2017-01-01
Java 實戰(zhàn)項目之倉庫管理系統(tǒng)的實現(xiàn)流程
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)一個倉庫管理系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11
SpringBoot?@InitBinder注解綁定請求參數(shù)的過程詳解
這篇文章主要介紹了SpringBoot?@InitBinder注解綁定請求參數(shù),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04
Java 使用poi把數(shù)據(jù)庫中數(shù)據(jù)導入Excel的解決方法
本篇文章介紹了,Java 使用poi把數(shù)據(jù)庫中數(shù)據(jù)導入Excel的解決方法。需要的朋友參考下2013-05-05
Spring工廠方法創(chuàng)建(實例化)bean實例代碼
這篇文章主要介紹了Spring工廠方法創(chuàng)建bean實例代碼,具有一定借鑒價值,需要的朋友可以參考下2018-01-01
spring中@Autowire和@Resource的區(qū)別在哪里(推薦)
這篇文章主要介紹了spring中@Autowire和@Resource的區(qū)別在哪里?本文結合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02

