springboot+springsecurity如何实现动态url细粒度权限认证

谨记:Url表只储存受保护的资源,不在表里的资源说明不受保护,任何人都可以访问

1、MyFilterInvocationSecurityMetadataSource 类判断该访问路径是否被保护

@Component
//用于设置受保护资源的权限信息的数据源
public class MyFilterInvocationSecurityMetadataSource implements
        FilterInvocationSecurityMetadataSource {
    @Bean
    public AntPathMatcher getAntPathMatcher(){
        return new AntPathMatcher();
    }    
    @Autowired  
	//获取数据库中的保存的url  Url表只储存受保护的资源,不在表里的资源说明不受保护,任何人都可以访问
    private RightsMapper rightsMapper; 
    
    @Autowired
    private AntPathMatcher antPathMatcher;
    @Override
    /*
     * @param 被调用的保护资源
     * @return 返回能够访问该保护资源的角色集合,如果没有,则应返回空集合。
     */
    public Collection<ConfigAttribute> getAttributes(Object object)
            throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        //获取用户请求的Url
        String url = fi.getRequestUrl();
        //先到数据库获取受权限控制的Url
        List<Rights> us = rightsMapper.queryAll();
        //用于储存用户请求的Url能够访问的角色
        Collection<ConfigAttribute> rs=new ArrayList<ConfigAttribute>();
        for(Rights u:us){
            if (u.getUrl() != null) {
                //逐一判断用户请求的Url是否和数据库中受权限控制的Url有匹配的
                if (antPathMatcher.match(u.getUrl(), url)) {
                    //如果有则将可以访问该Url的角色储存到Collection<ConfigAttribute>
                    rs.add(rightsMapper.queryById(u.getId()));
                }
            }
        }
        if(rs.size()>0) {
            return rs;
        }
        //没有匹配到,就说明此资源没有被控制,所有人都可以访问,返回null即可,返回null则不会进入之后的decide方法
        return null;
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        // TODO 自动生成的方法存根
        return null;
    }
    @Override
    public boolean supports(Class<?> clazz) {
        // TODO 自动生成的方法存根
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

rights表中的部分内容:

表结构

在这里插入图片描述

内容:

在这里插入图片描述

2、MyAccessDecisionManager 类判断该用户是否有权限访问

@Component
//用于设置判断当前用户是否可以访问被保护资源的逻辑
public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    /*
     * @param 请求该保护资源的用户对象
     * @param 被调用的保护资源
     * @param 有权限调用该资源的集合
     */
    public void decide(Authentication authentication, Object object,
                       Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        Iterator<ConfigAttribute> ite = configAttributes.iterator();
        //遍历configAttributes,查看当前用户是否有对应的权限访问该保护资源
        while (ite.hasNext()) {
            ConfigAttribute ca = ite.next();
            String needRole = ca.getAttribute();
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (ga.getAuthority().equals(needRole)) {
                    // 匹配到有对应角色,则允许通过
                    return;
                }
            }
        }
        // 该url有配置权限,但是当前登录用户没有匹配到对应权限,则禁止访问
        throw new AccessDeniedException("not allow");
    }
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

3、在SecurityConfig 类中配置说明

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    MyUserDetailsService myUserDetailsService;
    @Autowired
    private SendSmsSecurityConfig sendSmsSecurityConfig;
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
    //加密机制
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance(); // 不加密
    }
    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService)
                .passwordEncoder(passwordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()//对请求授权
                .antMatchers("/**").permitAll()
                .anyRequest()//任何请求
                .authenticated()//登录后访问
                .withObjectPostProcessor(
                        new ObjectPostProcessor<FilterSecurityInterceptor>() {
                            @Override
                            public <O extends FilterSecurityInterceptor> O postProcess(
                                    O fsi) {
                                fsi.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                                fsi.setAccessDecisionManager(myAccessDecisionManager);
                                return fsi;
                            }
                        })
                .and().csrf().disable();
    }
}

配置如下代码:

在这里插入图片描述

至此完成所有配置!!!

SpringSecurity解决公共接口自定义权限验证失效问题,和源码分析

背景:

自定义权限认证,一部分接口必须要有相应的角色权限,一部分接口面向所有访问者,一部分接口任何人都不能访问。但是在使用 SpringSecurity的过程中发现,框架会将没有指定角色列表的URL资源直接放行,不做拦截。

用户登录认证成功后,携带Token访问URL资源,spring security 根据Token(请求头Authorization中)来分辨不同用户。

用户权限数据源是一个Map:以 URL资源为Key,以有权访问的Key的角色列表为Value。

使用时发现当一个接口有Key,但是Value为空或null时,spring security 框架自动放行,导致了权限失效问题。

解决方法有两种:

第一种方法:

默认rejectPublicInvocations为false。

对需要控制权限的URL资源添加标志,以防止roleList为空,跳过了权限验证.

公共权限设置为null,不进行权限验证

第二种方法:

配置rejectPublicInvocations为true

此后roleList为空,或者没有找到URL资源时,都为拒绝访问

需要控制权限的URL资源,即使对应角色为空,也会进行权限验证

公共权限设置为所有角色和匿名角色,不进行权限验证

package org.springframework.security.access.intercept;
/**
 * 对安全对象(访问请求+用户主体)拦截的抽象类源码
 */
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {
	// ... 其他方法省略
	
	protected InterceptorStatusToken beforeInvocation(Object object) {
		Assert.notNull(object, "Object was null");
		final boolean debug = logger.isDebugEnabled();
		if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
			throw new IllegalArgumentException(
					"Security invocation attempted for object "
							+ object.getClass().getName()
							+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
							+ getSecureObjectClass());
		}
		// 从权限数据源获取了当前 <URL资源> 对应的 <角色列表>
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
				
		// 框架在此处判断URL资源对应的角色列表是否为空
		if (attributes == null || attributes.isEmpty()) {
			// rejectPublicInvocations默认为false 
			// 可以配置为true,即角色列表为空的时候不进行放行
			if (rejectPublicInvocations) {
				throw new IllegalArgumentException(
						"Secure object invocation "
								+ object
								+ " was denied as public invocations are not allowed via this interceptor. "
								+ "This indicates a configuration error because the "
								+ "rejectPublicInvocations property is set to 'true'");
			}
			if (debug) {
				logger.debug("Public object - authentication not attempted");
			}
			publishEvent(new PublicInvocationEvent(object));
			return null; // no further work post-invocation
		}
		if (debug) {
			logger.debug("Secure object: " + object + "; Attributes: " + attributes);
		}
		
		// 如果当前用户权限对象为null
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}
		Authentication authenticated = authenticateIfRequired();
		// Attempt authorization,此处调用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);
		}
	}
	// ... 其他方法略
}

以上为个人经验,希望能给大家一个参考。

猜你在找的springboot+springsecurity如何实现动态url细粒度权限认证相关文章

今天给大家带来的是关于Java的相关知识,文章围绕着comparable和comparator的区别展开,文中有非常详细的介绍及代码示例,需求的大佬可以参考下
本文将向您展示如何使用Spring Boot创建一个的Spring Batch的Hello World示例。对和我一样入门的有一定的帮助,感兴趣的小伙伴们可以参考一下
了springboot+springsecurity如何实现动态url细粒度权限认证的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
Semaphore一般译作信号量,它也是一种线程同步工具,主要用于多个线程对共享资源进行并行操作的一种工具类。它代表了一种许可的概念,是否允许多线程对同一资源进行操
今天给大家带来的是关于Java的相关知识,文章围绕着Java常见的排序算法展开,文中有非常详细的介绍及代码示例,需求的大佬可以参考下
TCP是一种传输控制协议,是面向连接的、可靠的、基于字节流之间的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,TCP完成第四层传输层所指定的功
今天给大家带来的是关于Java的相关知识,文章围绕着Java如何实现树的同构展开,文中有非常详细的介绍及代码示例,需求的大佬可以参考下
本文主要介绍了MyBatis-Plus 使用枚举自动关联注入,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起
本文主要介绍了MyBatis-Plus通用枚举自动关联注入的实现,解决了繁琐的配置,让 mybatis 优雅的使用枚举属性,感兴趣的可以一起来了解一下
了SpringBoot集成Redis,并自定义对象序列化操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
为了方便邮件发送功能的使用,我们用邮件发送功能实现用户注册,实现步骤大概就是进行用户注册同时发送一封激活邮件,邮件里附带激活链接,关于Springboot发送邮件注册激
今天给大家带来的是关于Java的相关知识总结,文章围绕着Java循环队列与非循环队列的区别展开,文中有非常详细的介绍及代码示例,需求的大佬可以参考下