Spring Security方法鑒權(quán)的實(shí)現(xiàn)
介紹
在Spring Security中,主要有兩種鑒權(quán)方式,一個(gè)是基于web請(qǐng)求的鑒權(quán),一個(gè)是基于方法的鑒權(quán)。無論哪種鑒權(quán),都最終會(huì)交由AuhtorizationManager執(zhí)行權(quán)限檢查。
@FunctionalInterface public interface AuthorizationManager<T> { default void verify(Supplier<Authentication> authentication, T object) { AuthorizationDecision decision = check(authentication, object); if (decision != null && !decision.isGranted()) { throw new AccessDeniedException("Access Denied"); } } @Nullable AuthorizationDecision check(Supplier<Authentication> authentication, T object); }
從AuthorizationManager#check方法可以看出,如果要執(zhí)行權(quán)限檢查,那么必要的兩個(gè)要素是Authentication和被保護(hù)的對(duì)象。
Authenticaion已經(jīng)在登錄過程中保存到了SecurityContext中,是拿來直接用的對(duì)象
被保護(hù)的對(duì)象(即:secureObject),原則上可以是任何類型。在實(shí)際的應(yīng)用中,主要是以下幾個(gè):
- HttpServletRequest,在對(duì)根據(jù)路徑進(jìn)行模式匹配時(shí)使用
- RequestAuthorizationContext,在對(duì)根據(jù)表達(dá)式對(duì)Web請(qǐng)求執(zhí)行權(quán)限檢查時(shí)使用
- MethodInvocation,在執(zhí)行方法鑒權(quán)時(shí)使用
基于web請(qǐng)求的鑒權(quán),可以通過配置SecurityFilterChain來根據(jù)請(qǐng)求的Path、Method等檢查權(quán)限。比如:
http .authorizeHttpRequests(requests -> requests .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll() .requestMatchers("/login/redirect").permitAll() .requestMatchers("/secured/foo").hasAuthority("P0") .anyRequest().authenticated());
對(duì)于方法鑒權(quán),通常是通過annotation來進(jìn)行的。
方法鑒權(quán)實(shí)戰(zhàn)
常用的有四個(gè)注解:@PreAuthorize,@PostAuthorize,@PreFilter以及@PostFilter。他們的使用非常簡單,如下:
標(biāo)準(zhǔn)方式
在配置類上添加注解:@EnableMethodSecurity
@Configuration @EnableMethodSecurity public class SomeConfiguration { // ... }
在Service或者Controller的方法上添加相應(yīng)注解
@GetMapping("/other") @PreAuthorize("hasAuthority('P1')") // 擁有P1權(quán)限才可以方法該方法 public String other(HttpSession session) { return getUsername() + "其他資源: " + session.getId(); }
注:@PreAuthorize等注解的參數(shù)中,之所以能夠使用一些內(nèi)置對(duì)象和方法(比如:hasRole、returnObject,principal),是因?yàn)槭褂玫纳舷挛膶?duì)象中,有一個(gè)root對(duì)象(MethodSecurityExpressionOperations),所有這些注解中使用的內(nèi)置對(duì)象和方法都來自它。
擴(kuò)展
有些時(shí)候,默認(rèn)的方式不能滿足業(yè)務(wù)需求,比如:從Authentication#getAuthorities得到的信息不足以滿足業(yè)務(wù)需求,需要從數(shù)據(jù)庫中查詢數(shù)據(jù)。此時(shí)就需要擴(kuò)展Spring Security的授權(quán)功能。
從擴(kuò)展范圍從小到大可以分為如下三種擴(kuò)展方式:
- 自定義Bean,然后提供權(quán)限檢查方法
- 自定義MethodSecurityExpressionHandler
- 指定自己的AuthrozationManager實(shí)現(xiàn)
自定義Bean
這種方式,是完全無侵入的擴(kuò)展,只需要向Spring容器注冊(cè)一個(gè)Bean,給一個(gè)名字,然后接可以在@PreAuthorize等注解中使用這個(gè)bean的方法。
定義Bean
@Component("authz") public class CipherAuthorization { public boolean hasPerm(String permission) { // 從數(shù)據(jù)庫中查詢當(dāng)前登錄用戶的所有權(quán)限 // 查看permission是否在返回的權(quán)限集合之中,是則返回true,否則false boolean foundMatch = ... return foundMatch; } }
在業(yè)務(wù)類中使用
@Service public class MyService { @PreAuthorize("@authz.hasPerm('system:edit')") public void updateData(...) { //... } }
自定義MethodSecurityExpressionHandler
這種方式,可以修改解析@PreAuthorize表達(dá)式的方式。通常我們可以復(fù)用DefaultMethodSecurityExpressionHandler,或者實(shí)現(xiàn)一個(gè)它的子類。無論哪種方式,都是對(duì)這個(gè)Handler進(jìn)行了定制。比如:
@Bean static MethodSecurityExpressionHandler methodSecurityExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); // 定制handler,比如指定一個(gè)RoleHierarchy return handler; }
指定自己的AuthorizationManager
這種方式,是徹底定制化了權(quán)限檢查的整個(gè)過程,完全使用我們自己定義的AuthorizationManager實(shí)現(xiàn)類。比如:
先定一個(gè)自定義的AuthorizationManager類:
@Component public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> { @Override public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) { // 執(zhí)行自己的權(quán)限檢查 } }
然后,在Configuration中指定它:
@Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) Advisor preAuthorize(MyAuthorizationManager manager) { return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager); }
原理分析
配置分析
在方法鑒權(quán)中,使用了Spring AOP(當(dāng)然,也可以指定AspectJ實(shí)現(xiàn))來攔截被注解的方法。每個(gè)注解都對(duì)應(yīng)一個(gè)Advisor。這一點(diǎn),可以通過@EnableMethodSecurity這個(gè)注解查看。
//... @Import(MethodSecuritySelector.class) public @interface EnableMethodSecurity { //... }
這里import了MethodSecuritySelector,它的主要內(nèi)容如下:
if (annotation.prePostEnabled()) { imports.add(PrePostMethodSecurityConfiguration.class.getName()); } if (annotation.securedEnabled()) { imports.add(SecuredMethodSecurityConfiguration.class.getName()); } if (annotation.jsr250Enabled()) { imports.add(Jsr250MethodSecurityConfiguration.class.getName()); }
對(duì)于@PreAuthorize和PostAuthorize兩個(gè)注解來說,使用到了同一個(gè)配置類:PrePostMethodSecurityConfiguration。
這里拿@PreAuthorize來說,這個(gè)類的主要內(nèi)容如下:
@Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) Advisor preAuthorizeAuthorizationMethodInterceptor(...) { PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); manager.setExpressionHandler( expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context))); AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor .preAuthorize(manager(manager, registryProvider)); strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy); eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher); return preAuthorize; }
可以看到,處理@PreAuthorize注解的Advisor是AuthorizationManagerBeforeMethodInterceptor,而AuthorizationManager是PreAuthorizeAuthorizationManager。
運(yùn)行分析
有了上述的配置,再來看運(yùn)行。
首先看AuthorizationManagerBeforeMethodInterceptor,在這個(gè)類里面,可以看到如下方法:
@Override public Object invoke(MethodInvocation mi) throws Throwable { attemptAuthorization(mi); return mi.proceed(); }
它用來開啟權(quán)限檢查,而權(quán)限檢查本身其實(shí)是通過調(diào)用AuthorizationManager#check方法來進(jìn)行的。
接下來,我們?cè)倏碢reAuthorizeAuthorizationManager,這個(gè)類是處理@PreAuthorize注解的授權(quán)管理器。它的主要內(nèi)容如下:
@Override public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) { ExpressionAttribute attribute = this.registry.getAttribute(mi); if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { return null; } EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi); boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx); return new ExpressionAuthorizationDecision(granted, attribute.getExpression()); }
可以看到,它首先從registry中找到MethodSecurityExpressionHandler,然后通過調(diào)用它的createEvaluationContext方法獲取EvaluationContext,然后對(duì)@PreAuthorize的參數(shù)(SpEL表達(dá)式)進(jìn)行計(jì)算,得到一個(gè)布爾值,決定是否通過權(quán)限檢查。
到此這篇關(guān)于Spring Security方法鑒權(quán)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity方法鑒權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證和鑒權(quán)全過程
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot整合SpringSecurityOauth2實(shí)現(xiàn)鑒權(quán)動(dòng)態(tài)權(quán)限問題
- SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)的實(shí)現(xiàn)
- SpringSecurity動(dòng)態(tài)加載用戶角色權(quán)限實(shí)現(xiàn)登錄及鑒權(quán)功能
相關(guān)文章
SpringBoot應(yīng)用整合ELK實(shí)現(xiàn)日志收集的示例代碼
這篇文章主要介紹了SpringBoot應(yīng)用整合ELK實(shí)現(xiàn)日志收集的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09java、spring、springboot中整合Redis的詳細(xì)講解
這篇文章主要介紹了java、spring、springboot中整合Redis的詳細(xì)講解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Spring boot通過切面,實(shí)現(xiàn)超靈活的注解式數(shù)據(jù)校驗(yàn)過程
這篇文章主要介紹了Spring boot通過切面,實(shí)現(xiàn)超靈活的注解式數(shù)據(jù)校驗(yàn)過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Swagger3.0 整合spring boot2.7x避免swagger2.0與boot2.7沖突
這篇文章主要介紹了Swagger3.0 整合spring boot2.7x避免swagger2.0與boot2.7沖突問題,通過注釋掉2.0引入的倆包,直接引入3.0,文中結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-10-10SpringBoot模擬員工數(shù)據(jù)庫并實(shí)現(xiàn)增刪改查操作
這篇文章主要給大家介紹了關(guān)于SpringBoot模擬員工數(shù)據(jù)庫并實(shí)現(xiàn)增刪改查操作的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09SpringMVC統(tǒng)一異常處理實(shí)例代碼
這篇文章主要介紹了SpringMVC統(tǒng)一異常處理實(shí)例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11