Spring?Security權(quán)限控制的實現(xiàn)接口
本文樣例代碼地址:spring-security-oauth2.0-sample。
關(guān)于此章,官網(wǎng)介紹:Authorization
本文使用Spring Boot 2.7.4版本,對應Spring Security 5.7.3版本。
Introduction
認證過程中會一并獲得用戶權(quán)限,Authentication#getAuthorities
接口方法提供權(quán)限,認證過后即是鑒權(quán),Spring Security使用GrantedAuthority
接口代表權(quán)限。早期版本在FilterChain
中使用FilterSecurityInterceptor
中執(zhí)行鑒權(quán)過程,現(xiàn)使用AuthorizationFilter
執(zhí)行,開始執(zhí)行順序兩者一致,此外,Filter
中具體實現(xiàn)也由 AccessDecisionManager
+ AccessDecisionVoter
變?yōu)?AuthorizationManager
本文關(guān)注新版本的實現(xiàn):AuthorizationFilter
和AuthorizationManager
。
AuthorizationManager
最常用的實現(xiàn)類為RequestMatcherDelegatingAuthorizationManager
,其中會根據(jù)你的配置生成一系列RequestMatcherEntry
,每個entry中包含一個匹配器RequestMatcher
和泛型類被匹配對象。
UML類圖結(jié)構(gòu)如下:
另外,對于 method security ,實現(xiàn)方式主要為AOP+Spring EL,常用權(quán)限方法注解為:
@EnableMethodSecurity
@PreAuthorize
@PostAuthorize
@PreFilter
@PostFilter
@Secured
這些注解可以用在controller方法上用于權(quán)限控制,注解中填寫Spring EL表述權(quán)限信息。這些注解一起使用時的執(zhí)行順序由枚舉類AuthorizationInterceptorsOrder
控制:
public enum AuthorizationInterceptorsOrder { FIRST(Integer.MIN_VALUE), /** * {@link PreFilterAuthorizationMethodInterceptor} */ PRE_FILTER, PRE_AUTHORIZE, SECURED, JSR250, POST_AUTHORIZE, /** * {@link PostFilterAuthorizationMethodInterceptor} */ POST_FILTER, LAST(Integer.MAX_VALUE); ... }
而這些權(quán)限注解的提取和配置主要由org.springframework.security.config.annotation.method.configuration
包下的幾個配置類完成:
PrePostMethodSecurityConfiguration
SecuredMethodSecurityConfiguration
等
權(quán)限配置
權(quán)限配置可以通過兩種方式配置:
SecurityFilterChain
配置類配置@EnableMethodSecurity
開啟方法上注解配置
下面是關(guān)于SecurityFilterChain的權(quán)限配置,以及method security使用
@Configuration // 其中prepostEnabled默認true,其他注解配置默認false,需手動改為true @EnableMethodSecurity(securedEnabled = true) @RequiredArgsConstructor public class SecurityConfig { // 白名單 private static final String[] AUTH_WHITELIST = ... @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // antMatcher or mvcMatcher http.authorizeHttpRequests() .antMatchers(AUTH_WHITELIST).permitAll() // hasRole中不需要添加 ROLE_前綴 // ant 匹配 /admin /admin/a /admin/a/b 都會匹配上 .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated(); // denyAll慎用 // .anyRequest().denyAll(); // http.authorizeHttpRequests() // .mvcMatchers(AUTH_WHITELIST).permitAll() // // 效果同上 // .mvcMatchers("/admin").hasRole("ADMIN") // .anyRequest().denyAll(); } }
以@PreAuthorize
為例,在controller方法上使用:
@Api("user") @RestController @RequestMapping("/user") @RequiredArgsConstructor public class UserController { /** * {@link EnableMethodSecurity} 注解必須配置在配置類上<br/> * {@link PreAuthorize}等注解中表達式使用 Spring EL * @return */ @PreAuthorize("hasRole('ADMIN')") @GetMapping("/admin") public ResponseEntity<Map<String, Object>> admin() { return ResponseEntity.ok(Collections.singletonMap("msg","u r admin")); } }
源碼
配置類權(quán)限控制
AuthorizationFilter
public class AuthorizationFilter extends OncePerRequestFilter { // 在配置類中默認實現(xiàn)為 RequestMatcherDelegatingAuthorizationManager private final AuthorizationManager<HttpServletRequest> authorizationManager; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 委托給AuthorizationManager AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request); if (decision != null && !decision.isGranted()) { throw new AccessDeniedException("Access Denied"); } filterChain.doFilter(request, response); } }
來看看AuthorizationManager
默認實現(xiàn)RequestMatcherDelegatingAuthorizationManager
:
public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> { // http.authorizeHttpRequests().antMatchers(AUTH_WHITELIST)... // SecurityFilterChain中每配置一項就會增加一個Entry // RequestMatcherEntry包含一個RequestMatcher和一個待鑒權(quán)對象,這里是AuthorizationManager private final List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings; ... @Override public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) { for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) { RequestMatcher matcher = mapping.getRequestMatcher(); MatchResult matchResult = matcher.matcher(request); if (matchResult.isMatch()) { AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry(); return manager.check(authentication, new RequestAuthorizationContext(request, matchResult.getVariables())); } } return null; } }
方法權(quán)限控制
總的實現(xiàn)基于 AOP + Spring EL
以案例中 @PreAuthorize
注解的源碼為例
PrePostMethodSecurityConfiguration
@Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) final class PrePostMethodSecurityConfiguration { private final AuthorizationManagerBeforeMethodInterceptor preAuthorizeAuthorizationMethodInterceptor; private final PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager(); private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); ... @Autowired PrePostMethodSecurityConfiguration(ApplicationContext context) { // 設置 Spring EL 解析器 this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler); // 攔截@PreAuthorize方法 this.preAuthorizeAuthorizationMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor .preAuthorize(this.preAuthorizeAuthorizationManager); ... } ... }
AuthorizationManagerBeforeMethodInterceptor
基于AOP實現(xiàn)
public final class AuthorizationManagerBeforeMethodInterceptor implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { /** * 調(diào)用起點 */ public static AuthorizationManagerBeforeMethodInterceptor preAuthorize() { // 針對 @PreAuthorize注解提供的AuthorizationManager為PreAuthorizeAuthorizationManager return preAuthorize(new PreAuthorizeAuthorizationManager()); } /** * 初始化,創(chuàng)建基于@PreAuthorize注解的aop方法攔截器 * Creates an interceptor for the {@link PreAuthorize} annotation * @param authorizationManager the {@link PreAuthorizeAuthorizationManager} to use * @return the interceptor */ public static AuthorizationManagerBeforeMethodInterceptor preAuthorize( PreAuthorizeAuthorizationManager authorizationManager) { AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor( AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager); interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder()); return interceptor; } ... // 實現(xiàn)MethodInterceptor方法,在調(diào)用實際方法是會首先觸發(fā)這個 @Override public Object invoke(MethodInvocation mi) throws Throwable { // 先鑒權(quán) attemptAuthorization(mi); // 后執(zhí)行實際方法 return mi.proceed(); } private void attemptAuthorization(MethodInvocation mi) { // 判斷, @PreAuthorize方法用的manager就是 // PreAuthorizeAuthorizationManager // 是通過上面的static類構(gòu)造的 AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER, mi); if (decision != null && !decision.isGranted()) { throw new AccessDeniedException("Access Denied"); } ... } static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { throw new AuthenticationCredentialsNotFoundException( "An Authentication object was not found in the SecurityContext"); } return authentication; }; }
針對@PreAuthorize
方法用的manager就是 PreAuthorizeAuthorizationManager#check
,下面來看看
PreAuthorizeAuthorizationManager
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> { private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); @Override public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) { // 獲取方法上@PreAuthorize注解中的Spring EL 表達式屬性 ExpressionAttribute attribute = this.registry.getAttribute(mi); if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { return null; } // Spring EL 的 context EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi); // 執(zhí)行表達式中結(jié)果, 會執(zhí)行SecurityExpressionRoot類中對應方法。涉及Spring EL執(zhí)行原理,pass boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx); // 返回結(jié)果 return new ExpressionAttributeAuthorizationDecision(granted, attribute); } }
到此這篇關(guān)于Spring Security權(quán)限控制的實現(xiàn)接口的文章就介紹到這了,更多相關(guān)Spring Security權(quán)限控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實現(xiàn)攔截器、過濾器、監(jiān)聽器過程解析
這篇文章主要介紹了SpringBoot實現(xiàn)攔截器、過濾器、監(jiān)聽器過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12深入淺析springboot中static和templates區(qū)別
這篇文章主要介紹了springboot中static和templates區(qū)別,本文通過圖文實例代碼相結(jié)合給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02解決springboot configuration processor對maven子模塊不起作用的問題
這篇文章主要介紹了解決springboot configuration processor對maven子模塊不起作用的問題,本文通過圖文實例代碼給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09基于指針pointers和引用references的區(qū)別分析
本篇文章介紹了,基于指針pointers和引用references的區(qū)別分析。需要的朋友參考下2013-05-05MyBatis學習教程(四)-如何快速解決字段名與實體類屬性名不相同的沖突問題
我們經(jīng)常會遇到表中的字段名和表對應實體類的屬性名稱不一定都是完全相同的情況,如何解決呢?下面腳本之家小編給大家介紹MyBatis學習教程(四)-如何快速解決字段名與實體類屬性名不相同的沖突問題,一起學習吧2016-05-05