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類,下文有相關(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,獲取當前方法的安全屬性集合,該方法解析在目錄標題二 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進行補充說明:
- 當前的安全上下文中不存在認證信息(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)限表達式、角色信息等。
- 提供權(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)限進行驗證。這使得開發(fā)者可以在方法執(zhí)行前后定義細粒度的權(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注解的表達式 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)整為當前元素的數(shù)量 attrs.trimToSize(); return attrs; } }
// 解析注解中的表達式,創(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注解的表達式 @Override public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute, String filterObject, String preAuthorizeAttribute) { try { // SpEL表達式解析器 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表達式
源碼分析
// 源碼存在部分刪減,僅展示分析與@PreAuthorize相關(guān)的內(nèi)容 public class ExpressionBasedPreInvocationAdvice implements PreInvocationAuthorizationAdvice { // 關(guān)鍵類 第四點有對該類的關(guā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)鍵步驟 計算表達式值 return (preAuthorize != null) ? ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true; } }
ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx方法補充說明:
根據(jù)提供的安全表達式和評估上下文 ctx 來評估安全表達式的結(jié)果,并返回一個布爾值。true,則權(quán)限校驗通過;false,則校驗失敗。
四、DefaultMethodSecurityExpressionHandler類
作用
- 創(chuàng)建評估上下文:在安全表達式求值之前,DefaultMethodSecurityExpressionHandler 會創(chuàng)建一個評估上下文EvaluationContext對象,以提供給安全表達式進行求值。評估上下文包含了當前用戶的身份驗證信息、目標對象和方法參數(shù)等相關(guān)信息。
- 權(quán)限注解的處理:DefaultMethodSecurityExpressionHandler 支持處理方法參數(shù)上的權(quán)限注解,例如 @PreFilter 和 @PostFilter 注解。它會將這些注解解析為相應(yīng)的安全表達式,并在評估上下文中傳遞方法參數(shù)的信息,以進行權(quán)限過濾操作。
源碼分析
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方法中調(diào)用該方法,創(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方法在方法級別的安全表達式求值過程中被調(diào)用,其主要作用是創(chuàng)建一個評估上下文對象,以提供給安全表達式進行求值。 */ @Override public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) { return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer()); } /** * 方法級別的安全表達式通常需要訪問當前用戶、目標對象和方法參數(shù)等相關(guān) 信息。createEvaluationContextInternal方法會使用 MethodSecurityExpressionRoot類的實例作為權(quá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; } }
對 MethodSecurityExpressionOperations 類進行補充說明:
MethodSecurityExpressionOperations 接口定義了一組方法,用于在安全表達式中進行常見的操作和判斷,例如獲取當前用戶信息、檢查角色和權(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 類進行補充說明: 在 Spring Security 中,當使用方法級別的注解(如 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter)時,需要引用方法參數(shù)的名稱來進行安全性評估和過濾操作。但編譯器默認情況下不會在編譯過程中保留方法參數(shù)的名稱,而是使用類似 “arg0”、“arg1” 等默認名稱。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)?;趕pring?boot寫定時任務(wù),并且是使用注解的方式進行實現(xiàn),分成的方便將自己的類注入spring容器,需要的朋友可以參考下2024-01-01詳解java集成支付寶支付接口(JSP+支付寶20160912)
本篇文章主要介紹了java集成支付寶支付接口,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12IDEA JarEditor編輯jar包方式(直接新增,修改,刪除jar包內(nèi)的class文件)
文章主要介紹了如何使用IDEA的JarEditor插件直接修改jar包內(nèi)的class文件,而不需要手動解壓、反編譯和重新打包,通過該插件,可以更方便地進行jar包的修改和測試2025-01-01Spring裝配Bean之用Java代碼安裝配置bean詳解
這篇文章主要給大家介紹了關(guān)于Spring裝配Bean之用Java代碼安裝配置bean的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用spring具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧。2017-10-10Java中JSONObject和Map<String,?Object>的轉(zhuǎn)換方法
平時對接口時,經(jīng)常遇到j(luò)son字符串和map對象之間的交互,這篇文章主要給大家介紹了關(guān)于Java中JSONObject和Map<String,?Object>的轉(zhuǎn)換方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-07-07