温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么在spring security中动态配置url权限

发布时间:2021-05-31 17:14:30 来源:亿速云 阅读:499 作者:Leah 栏目:编程语言

这期内容当中小编将会给大家带来有关怎么在spring security中动态配置url权限,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

spring security 授权回顾

spring security 通过FilterChainProxy作为注册到web的filter,FilterChainProxy里面一次包含了内置的多个过滤器,我们首先需要了解spring security内置的各种filter:

AliasFilter ClassNamespace Element or Attribute
CHANNEL_FILTERChannelProcessingFilterhttp/intercept-url@requires-channel
SECURITY_CONTEXT_FILTERSecurityContextPersistenceFilterhttp
CONCURRENT_SESSION_FILTERConcurrentSessionFiltersession-management/concurrency-control
HEADERS_FILTERHeaderWriterFilterhttp/headers
CSRF_FILTERCsrfFilterhttp/csrf
LOGOUT_FILTERLogoutFilterhttp/logout
X509_FILTERX509AuthenticationFilterhttp/x509
PRE_AUTH_FILTERAbstractPreAuthenticatedProcessingFilter SubclassesN/A
CAS_FILTERCasAuthenticationFilterN/A
FORM_LOGIN_FILTERUsernamePasswordAuthenticationFilterhttp/form-login
BASIC_AUTH_FILTERBasicAuthenticationFilterhttp/http-basic
SERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareRequestFilterhttp/@servlet-api-provision
JAAS_API_SUPPORT_FILTERJaasApiIntegrationFilterhttp/@jaas-api-provision
REMEMBER_ME_FILTERRememberMeAuthenticationFilterhttp/remember-me
ANONYMOUS_FILTERAnonymousAuthenticationFilterhttp/anonymous
SESSION_MANAGEMENT_FILTERSessionManagementFiltersession-management
EXCEPTION_TRANSLATION_FILTERExceptionTranslationFilterhttp
FILTER_SECURITY_INTERCEPTORFilterSecurityInterceptorhttp
SWITCH_USER_FILTERSwitchUserFilterN/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中动态配置url权限了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI