Spring Security方法鑒權(quán)的實現(xiàn)
介紹
在Spring Security中,主要有兩種鑒權(quán)方式,一個是基于web請求的鑒權(quán),一個是基于方法的鑒權(quán)。無論哪種鑒權(quán),都最終會交由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)限檢查,那么必要的兩個要素是Authentication和被保護(hù)的對象。
Authenticaion已經(jīng)在登錄過程中保存到了SecurityContext中,是拿來直接用的對象
被保護(hù)的對象(即:secureObject),原則上可以是任何類型。在實際的應(yīng)用中,主要是以下幾個:
- HttpServletRequest,在對根據(jù)路徑進(jìn)行模式匹配時使用
- RequestAuthorizationContext,在對根據(jù)表達(dá)式對Web請求執(zhí)行權(quán)限檢查時使用
- MethodInvocation,在執(zhí)行方法鑒權(quán)時使用
基于web請求的鑒權(quán),可以通過配置SecurityFilterChain來根據(jù)請求的Path、Method等檢查權(quán)限。比如:
http
.authorizeHttpRequests(requests -> requests
.dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
.requestMatchers("/login/redirect").permitAll()
.requestMatchers("/secured/foo").hasAuthority("P0")
.anyRequest().authenticated());對于方法鑒權(quán),通常是通過annotation來進(jìn)行的。
方法鑒權(quán)實戰(zhàn)
常用的有四個注解:@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)置對象和方法(比如:hasRole、returnObject,principal),是因為使用的上下文對象中,有一個root對象(MethodSecurityExpressionOperations),所有這些注解中使用的內(nèi)置對象和方法都來自它。
擴(kuò)展
有些時候,默認(rèn)的方式不能滿足業(yè)務(wù)需求,比如:從Authentication#getAuthorities得到的信息不足以滿足業(yè)務(wù)需求,需要從數(shù)據(jù)庫中查詢數(shù)據(jù)。此時就需要擴(kuò)展Spring Security的授權(quán)功能。
從擴(kuò)展范圍從小到大可以分為如下三種擴(kuò)展方式:
- 自定義Bean,然后提供權(quán)限檢查方法
- 自定義MethodSecurityExpressionHandler
- 指定自己的AuthrozationManager實現(xiàn)
自定義Bean
這種方式,是完全無侵入的擴(kuò)展,只需要向Spring容器注冊一個Bean,給一個名字,然后接可以在@PreAuthorize等注解中使用這個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,或者實現(xiàn)一個它的子類。無論哪種方式,都是對這個Handler進(jìn)行了定制。比如:
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
// 定制handler,比如指定一個RoleHierarchy
return handler;
}指定自己的AuthorizationManager
這種方式,是徹底定制化了權(quán)限檢查的整個過程,完全使用我們自己定義的AuthorizationManager實現(xiàn)類。比如:
先定一個自定義的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實現(xiàn))來攔截被注解的方法。每個注解都對應(yīng)一個Advisor。這一點(diǎn),可以通過@EnableMethodSecurity這個注解查看。
//...
@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());
}對于@PreAuthorize和PostAuthorize兩個注解來說,使用到了同一個配置類:PrePostMethodSecurityConfiguration。
這里拿@PreAuthorize來說,這個類的主要內(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,在這個類里面,可以看到如下方法:
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
attemptAuthorization(mi);
return mi.proceed();
}它用來開啟權(quán)限檢查,而權(quán)限檢查本身其實是通過調(diào)用AuthorizationManager#check方法來進(jìn)行的。
接下來,我們再看PreAuthorizeAuthorizationManager,這個類是處理@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,然后對@PreAuthorize的參數(shù)(SpEL表達(dá)式)進(jìn)行計算,得到一個布爾值,決定是否通過權(quán)限檢查。
到此這篇關(guān)于Spring Security方法鑒權(quán)的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity方法鑒權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Feign實現(xiàn)多文件上傳,Open?Feign多文件上傳問題及解決
這篇文章主要介紹了Feign實現(xiàn)多文件上傳,Open?Feign多文件上傳問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
Intellij IDEA連接Navicat數(shù)據(jù)庫的方法
這篇文章主要介紹了Intellij IDEA連接Navicat數(shù)據(jù)庫的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借價值,需要的朋友可以參考下2021-03-03
簡單介紹區(qū)分applet和application的方法
applet和application都是Java語言編寫出來的應(yīng)用程序,本文簡單介紹了二者的不同之處,需要的朋友可以參考下2017-09-09
springboot對接minio的webhook完整步驟記錄
Minio是一款開源的對象存儲服務(wù),它致力于為開發(fā)者提供簡單、高性能、高可用的云存儲解決方案,下面這篇文章主要給大家介紹了關(guān)于springboot對接minio的webhook的相關(guān)資料,需要的朋友可以參考下2024-07-07
SpringBoot實現(xiàn)TCP連接并進(jìn)行數(shù)據(jù)互傳的方法
本文詳細(xì)介紹了微服務(wù)架構(gòu)中的翻譯組件使用場景,以及多種開源翻譯組件的解決方案,文中分析了國內(nèi)外多個翻譯服務(wù)如百度翻譯、谷歌翻譯等,以及如何在微服務(wù)項目中集成這些翻譯組件,感興趣的朋友跟隨小編一起看看吧2024-11-11
Springboot+rabbitmq實現(xiàn)延時隊列的兩種方式
這篇文章主要介紹了Springboot+rabbitmq實現(xiàn)延時隊列的兩種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
SpringBoot中的@RequestMapping注解的用法示例
@RequestMapping注解是SpringBoot中最常用的注解之一,它可以幫助開發(fā)者定義和處理HTTP請求,本篇文章我們將詳細(xì)為大家介紹如何使用SpringBoot中的@RequestMapping注解,感興趣的同學(xué)跟著小編一起來學(xué)習(xí)吧2023-06-06
使用Java實現(xiàn)查看線程的運(yùn)行狀態(tài)(附源碼)
在現(xiàn)代 Java 應(yīng)用中,線程的運(yùn)行狀態(tài)對于排查問題和優(yōu)化性能具有至關(guān)重要的作用,本文將使用Java編寫一個查看線程運(yùn)行狀態(tài)的工具,有需要的可以了解下2025-03-03

