springboot+springsecurity如何实现动态url细粒度权限认证
作者:佛说 发布时间:2023-10-01 04:06:39
标签:springboot,springsecurity,权限,认证
谨记: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);
}
}
// ... 其他方法略
}
来源:https://blog.csdn.net/weixin_45498999/article/details/107386982


猜你喜欢
- 背景 我们知道在.NET Framework中存在四种常用的定时器,他们分别是:1 两个是通用的多线程定时器:Syste
- 我自己在使用的过程中遇见的问题,百度找了很久才找到合适的方法。报错如下:Emulator: emulator: ERROR: Unknown
- 首先创建一个用户控件 如下图 用到的控件 label、button、TextBox 内部代码如下#region 分页字段和属性pr
- Eclipse 开发java 出现Failed to create the Java Virtual Machine错误解决办法一直用Ecl
- 目录重入锁1. 实现重进入2. 公平与非公平获取锁的区别读写锁1. 接口示例2. 读写状态的设计3. 写锁的获取与释放4. 读锁的获取与释放
- RocketMQ修改生产者消费者日志保存路径rocket默认是将所有日志文件保存到user.home的对于win系统就是C盘了。1.修改Ro
- 一、效果展示初级难度中级难度高级难度测试界面二、项目介绍项目背景扫雷是一款大众类的益智小游戏。根据点击格子出现的数字找出所有非雷格子,同时避
- 刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向
- 本文实例讲述了Spring实战之SpEl语法。分享给大家供大家参考,具体如下:一 Beanpackage org.crazyit.app.d
- 对于QQ截图,肯定是早就有认识了,只是一直没有去认真观察这个操作的具体实现步骤。所以这里将自己的记忆中的步骤简单的写一下:习惯性用QQ或者T
- maven插件主要是为maven中生命周期中的阶段服务的,maven中只是定义了3套生命周期,以及每套生命周期中有哪些阶段,具体每个阶段中执
- Java try和catch的使用尽管由Java运行时系统提供的默认异常处理程序对于调试是很有用的,但通常你希望自己处理异常。这样做有两个好
- 前言老了,老了,那天有位小同事问我Android跳转三方应用时有什么要注意的?是否可以直接跳?如何传递参数过去? 嗯… 我竟然说需要root
- 这篇文章主要介绍了java读取xml配置参数代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友
- 在正常开发中,我们客户端需要用webView加载网页,再遇到网络慢或者访问的服务器响应时,页面是空白的,所以为了用户更好的体验,我们可以提供
- 本文实例讲述了Android实现学生管理系统,分享给大家供大家参考。具体如下:(1)管理系统实现的功能主要是:学生、教师的注册登录,和选课,
- 目录切换语言核心代码使用dragonFace改系统语言本篇简单介绍将在Android App中进行语言的切换和使用dragonFace改系统
- spring-cloud-config 配置中心实现Spring Cloud Config 用于为分布式系统中的基础设施和微服务应用提供集中
- 前几天客户提需求,对App增加一个功能,这个功能目前市面上已经很常见,那就是应用内切换语言。啥意思,就是 英、中、法、德、日。。。语言随意切
- 通过View提供的方法获取高度方式有两种:1, 当前显示的view中直接获取当前view高宽2, 通过Activity的getWindow(