国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > Java > 正文

spring security動態配置url權限的2種實現方法

2024-07-14 08:41:12
字體:
來源:轉載
供稿:網友

緣起

標準的RABC, 權限需要支持動態配置,spring security默認是在代碼里約定好權限,真實的業務場景通常需要可以支持動態配置角色訪問權限,即在運行時去配置url對應的訪問角色。

基于spring security,如何實現這個需求呢?

最簡單的方法就是自定義一個Filter去完成權限判斷,但這脫離了spring security框架,如何基于spring security優雅的實現呢?

spring security 授權回顧

spring security 通過FilterChainProxy作為注冊到web的filter,FilterChainProxy里面一次包含了內置的多個過濾器,我們首先需要了解spring security內置的各種filter:

 

Alias Filter Class Namespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
HEADERS_FILTER HeaderWriterFilter http/headers
CSRF_FILTER CsrfFilter http/csrf
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AbstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareRequestFilter http/@servlet-api-provision
JAAS_API_SUPPORT_FILTER JaasApiIntegrationFilter http/@jaas-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A

 

最重要的是FilterSecurityInterceptor,該過濾器實現了主要的鑒權邏輯,最核心的代碼在這里:

protected InterceptorStatusToken beforeInvocation(Object object) {  // 獲取訪問URL所需權限 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object);  Authentication authenticated = authenticateIfRequired(); // 通過accessDecisionManager鑒權 try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,  accessDeniedException)); throw accessDeniedException; } if (debug) { logger.debug("Authorization successful"); } if (publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs == null) { if (debug) { logger.debug("RunAsManager did not change Authentication object"); } // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,  attributes, object); } else { if (debug) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } }

從上面可以看出,要實現動態鑒權,可以從兩方面著手:

  • 自定義SecurityMetadataSource,實現從數據庫加載ConfigAttribute
  • 另外就是可以自定義accessDecisionManager,官方的UnanimousBased其實足夠使用,并且他是基于AccessDecisionVoter來實現權限認證的,因此我們只需要自定義一個AccessDecisionVoter就可以了

下面來看分別如何實現。

自定義AccessDecisionManager

官方的三個AccessDecisionManager都是基于AccessDecisionVoter來實現權限認證的,因此我們只需要自定義一個AccessDecisionVoter就可以了。

自定義主要是實現AccessDecisionVoter接口,我們可以仿照官方的RoleVoter實現一個:

public class RoleBasedVoter implements AccessDecisionVoter<Object> { @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { if(authentication == null) { return ACCESS_DENIED; } int result = ACCESS_ABSTAIN; Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication); for (ConfigAttribute attribute : attributes) { if(attribute.getAttribute()==null){ continue; } if (this.supports(attribute)) { result = ACCESS_DENIED; // Attempt to find a matching granted authority for (GrantedAuthority authority : authorities) {  if (attribute.getAttribute().equals(authority.getAuthority())) {  return ACCESS_GRANTED;  } } } } return result; } Collection<? extends GrantedAuthority> extractAuthorities( Authentication authentication) { return authentication.getAuthorities(); } @Override public boolean supports(Class clazz) { return true; }}

如何加入動態權限呢?

vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes)里的Object object的類型是FilterInvocation,可以通過getRequestUrl獲取當前請求的URL:

 FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl();

因此這里擴展空間就大了,可以從DB動態加載,然后判斷URL的ConfigAttribute就可以了。

如何使用這個RoleBasedVoter呢?在configure里使用accessDecisionManager方法自定義,我們還是使用官方的UnanimousBased,然后將自定義的RoleBasedVoter加入即可。

@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) .and() .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 自定義accessDecisionManager .accessDecisionManager(accessDecisionManager())  .and() .apply(securityConfigurerAdapter()); } @Bean public AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList( new WebExpressionVoter(), // new RoleVoter(), new RoleBasedVoter(), new AuthenticatedVoter()); return new UnanimousBased(decisionVoters); }

自定義SecurityMetadataSource

自定義FilterInvocationSecurityMetadataSource只要實現接口即可,在接口里從DB動態加載規則。

為了復用代碼里的定義,我們可以將代碼里生成的SecurityMetadataSource帶上,在構造函數里傳入默認的FilterInvocationSecurityMetadataSource。

public class AppFilterInvocationSecurityMetadataSource implements org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource { private FilterInvocationSecurityMetadataSource superMetadataSource; @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } public AppFilterInvocationSecurityMetadataSource(FilterInvocationSecurityMetadataSource expressionBasedFilterInvocationSecurityMetadataSource){  this.superMetadataSource = expressionBasedFilterInvocationSecurityMetadataSource;  // TODO 從數據庫加載權限配置 } private final AntPathMatcher antPathMatcher = new AntPathMatcher();  // 這里的需要從DB加載 private final Map<String,String> urlRoleMap = new HashMap<String,String>(){{ put("/open/**","ROLE_ANONYMOUS"); put("/health","ROLE_ANONYMOUS"); put("/restart","ROLE_ADMIN"); put("/demo","ROLE_USER"); }}; @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl(); for(Map.Entry<String,String> entry:urlRoleMap.entrySet()){  if(antPathMatcher.match(entry.getKey(),url)){  return SecurityConfig.createList(entry.getValue());  } } // 返回代碼定義的默認配置 return superMetadataSource.getAttributes(object); } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); }}

怎么使用?和accessDecisionManager不一樣,ExpressionUrlAuthorizationConfigurer 并沒有提供set方法設置FilterSecurityInterceptor的FilterInvocationSecurityMetadataSource,how to do?

發現一個擴展方法withObjectPostProcessor,通過該方法自定義一個處理FilterSecurityInterceptor類型的ObjectPostProcessor就可以修改FilterSecurityInterceptor。

@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http  .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)  .exceptionHandling()  .authenticationEntryPoint(problemSupport)  .accessDeniedHandler(problemSupport) .and()  .csrf()  .disable()  .headers()  .frameOptions()  .disable() .and()  .sessionManagement()  .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and()  .authorizeRequests()  // 自定義FilterInvocationSecurityMetadataSource  .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {  @Override  public <O extends FilterSecurityInterceptor> O postProcess(   O fsi) {   fsi.setSecurityMetadataSource(mySecurityMetadataSource(fsi.getSecurityMetadataSource()));   return fsi;  }  }) .and()  .apply(securityConfigurerAdapter()); } @Bean public AppFilterInvocationSecurityMetadataSource mySecurityMetadataSource(FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) { AppFilterInvocationSecurityMetadataSource securityMetadataSource = new AppFilterInvocationSecurityMetadataSource(filterInvocationSecurityMetadataSource); return securityMetadataSource;}

小結

本文介紹了兩種基于spring security實現動態權限的方法,一是自定義accessDecisionManager,二是自定義FilterInvocationSecurityMetadataSource。實際項目里可以根據需要靈活選擇。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 乡城县| 山阳县| 闵行区| 大渡口区| 正宁县| 九龙坡区| 含山县| 清镇市| 澜沧| 确山县| 方山县| 吉隆县| 涟水县| 保德县| 津南区| 革吉县| 庆元县| 会昌县| 象山县| 福安市| 喀什市| 肇州县| 绥宁县| 绩溪县| 汨罗市| 桃园市| 大方县| 西昌市| 凤山县| 施秉县| 蕲春县| 衡阳市| 台江县| 嘉鱼县| 乐清市| 炎陵县| 体育| 内黄县| 西充县| 南川市| 江口县|