spring security动态配置url权限的2种实现方法

缘起

标准的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。实际项目里可以根据需要灵活选择。

延伸阅读:

Spring Security 架构与源码分析

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • SpringBoot + Spring Security 基本使用及个性化登录配置详解

    Spring Security 基本介绍 这里就不对Spring Security进行过多的介绍了,具体的可以参考官方文档 我就只说下SpringSecurity核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) 基本环境搭建 这里我们以SpringBoot作为项目的基本框架,我这里使用的是maven的方式来进行的包管理,所以这里先给出集成Spring Security的方式 <dependencies> ... <dependency> <groupI

  • 使用Spring Security OAuth2实现单点登录

    1.概述 在本教程中,我们将讨论如何使用Spring Security OAuth和Spring Boot实现SSO - 单点登录. 我们将使用三个单独的应用程序: •授权服务器 - 这是中央身份验证机制 •两个客户端应用程序:使用SSO的应用程序 非常简单地说,当用户试图访问客户端应用程序中的安全页面时,他们将被重定向到首先通过身份验证服务器进行身份验证. 我们将使用OAuth2中的授权代码授权类型来驱动身份验证委派. 2.客户端应用程序 让我们从客户端应用程序开始;当然,我们将使用Sprin

  • 详解Spring Security如何配置JSON登录

    spring security用了也有一段时间了,弄过异步和多数据源登录,也看过一点源码,最近弄rest,然后顺便搭oauth2,前端用json来登录,没想到spring security默认居然不能获取request中的json数据,谷歌一波后只在stackoverflow找到一个回答比较靠谱,还是得要重写filter,于是在这里填一波坑. 准备工作 基本的spring security配置就不说了,网上一堆例子,只要弄到普通的表单登录和自定义UserDetailsService就可以.因为需

  • Spring Security基本配置方法解析

    Spring Security 是一个功能强大且可高度自定义的身份验证和访问控制框架. 它是保护基于Spring的应用程序的事实上的标准. Spring Security 是一个专注于为Java应用程序提供身份验证和授权的框架. 与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求 先建立一个maven多模块工程,如下: spring-security:父模块 spring-security-browser:处理浏览器相关的授权认证,最终作为

  • 详解spring security 配置多个AuthenticationProvider

    前言 发现很少关于spring security的文章,基本都是入门级的,配个UserServiceDetails或者配个路由控制就完事了,而且很多还是xml配置,国内通病...so,本文里的配置都是java配置,不涉及xml配置,事实上我也不会xml配置 spring security的大体介绍 spring security本身如果只是说配置,还是很简单易懂的(我也不知道网上说spring security难,难在哪里),简单不需要特别的功能,一个WebSecurityConfigurerA

  • Spring Boot中整合Spring Security并自定义验证代码实例

    最终效果 1.实现页面访问权限限制 2.用户角色区分,并按照角色区分页面权限 3.实现在数据库中存储用户信息以及角色信息 4.自定义验证代码 效果如下: 1.免验证页面 2.登陆页面 在用户未登录时,访问任意有权限要求的页面都会自动跳转到登陆页面. 3.需登陆才能查看的页面 用户登陆后,可以正常访问页面资源,同时可以正确显示用户登录名: 4.用户有角色区分,可以指定部分页面只允许有相应用户角色的人使用 4.1.只有ADMIN觉得用户才能查看的页面(权限不足) 4.2.只有ADMIN觉得用户才能查

  • 详解Spring Security 简单配置

    开发环境 maven idea jdk 1.8 tomcat 8 配置spring mvc + spring security pom.xml <properties> <spring.version>4.3.8.RELEASE</spring.version> <spring-sercurity.version>4.2.2.RELEASE</spring-sercurity.version> </properties> <de

  • spring security动态配置url权限的2种实现方法

    缘起 标准的RABC, 权限需要支持动态配置,spring security默认是在代码里约定好权限,真实的业务场景通常需要可以支持动态配置角色访问权限,即在运行时去配置url对应的访问角色. 基于spring security,如何实现这个需求呢? 最简单的方法就是自定义一个Filter去完成权限判断,但这脱离了spring security框架,如何基于spring security优雅的实现呢? spring security 授权回顾 spring security 通过FilterCh

  • Spring Security动态权限的实现方法详解

    目录 1. 动态管理权限规则 1.1 数据库设计 1.2 实战 2. 测试 最近在做 TienChin 项目,用的是 RuoYi-Vue 脚手架,在这个脚手架中,访问某个接口需要什么权限,这个是在代码中硬编码的,具体怎么实现的,松哥下篇文章来和大家分析,有的小伙伴可能希望能让这个东西像 vhr 一样,可以在数据库中动态配置,因此这篇文章和小伙伴们简单介绍下 Spring Security 中的动态权限方案,以便于小伙伴们更好的理解 TienChin 项目中的权限方案. 1. 动态管理权限规则 通

  • Spring Security如何使用URL地址进行权限控制

    这篇文章主要介绍了Spring Security如何使用URL地址进行权限控制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 目的是:系统内存在很多不同的用户,每个用户具有不同的资源访问权限,具体表现就是某个用户对于某个URL是无权限访问的.需要Spring Security忙我们过滤. FilterSecurityInterceptor是Spring Security进行URL权限判断的,FilterSecurityInterceptor又继

  • Spring security实现登陆和权限角色控制

     随笔简介 1.spring版本:4.3.2.RELEASE+spring security 版本:4.1.2.RELEASE(其它不做说明) 2.所展示内容全部用注解配置 3.springmvc已经配置好,不作说明 4.会涉及到springmvc,spel,el的东西,不熟悉的同学可以先去看一下这方面内容,特别是springmvc 首先想一下,登陆需要什么,最简单的情况下,用户名,密码,然后比对数据库,如果吻合就跳转到个人页面,否则回到登陆页面,并且提示用户名密码错误.这个过程中应该还带有权限

  • SpringBoot集成Spring security JWT实现接口权限认证

    1.添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjw

  • Spring Cloud动态配置刷新RefreshScope使用示例详解

    目录 引言 一.了解@RefreshScope,先要了解@Scope 二.RefreshScope 的实现原理 三.使用——@RefreshScope 使用流程 引言 用过Spring Cloud的同学都知道在使用动态配置刷新的我们要配置一个 @RefreshScope,在类上才可以实现对象属性的的动态更新. @RefreshScope 能实现动态刷新全仰仗着 @Scope这个注解. 一.了解@RefreshScope,先要了解@Scope 1.RefreshScope继承于GenericSco

  • Sentinel实现动态配置的集群流控的方法

    介绍 为什么要使用集群流控呢? 相对于单机流控而言,我们给每台机器设置单机限流阈值,在理想情况下整个集群的限流阈值为机器数量✖️单机阈值.不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流.因此仅靠单机维度去限制的话会无法精确地限制总体流量.而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果. 基于单机流量不均的问题以及如何设置集群整体的QPS的问题,我们需要创建一种集群限流的模式,这时候我们很自然地就想到,可以找一个 s

随机推荐