0


SpringSecurity从入门到放弃之RBAC权限认证(二)

1.概述

上文写到了SpringSecurity从入门到放弃之JWT认证登陆(一),该文章介绍了spring security的登陆原理以及整合JWT的登陆案例,本文将基于上文,介绍spring securtity整合RBAC权限模型,实现按权访问接口。

2.Spring Security整合RBAC权限模式

2.1 RBAC模型原理

RBAC(Role-Based Access Control):基于角色的权限访问控制。它的核心在于用户只和角色关联,而角色代表了权限,是一系列权限的集合。RBAC的核心元素包括:用户、角色、权限。

  • 用户:系统中所有的账户
  • 角色:一系列权限的集合(如:管理员,普通用户)
  • 权限:菜单,按钮,数据的增删改查等详细权限。

在RBAC中,权限与角色相关联,用户被分配成为适当角色下的成员而获得对应角色的权限。角色是为了完成不同的工作而被创建,用户则依据它的责任来被分配相应的角色,用户能被关联一个甚至多个角色,且能完成从一个角色向另一个角色的变更。角色可以根据场景或其他元素的变更,来被赋予新的权限,角色的权限亦能根据场景被收回。
用户、角色、权限之间主要联系如下图所示:
在这里插入图片描述
RBAC模型可以分为:RBAC 0、RBAC 1、RBAC 2、RBAC 3 四个阶段,一般公司使用RBAC0的模型就可以。RBAC 1、RBAC 2、RBAC 3都是基于RBAC 0上的改良版本。本文主要介绍RBAC 0模型:用户与角色、角色与权限都是多对多的关系。如下图所示:
在这里插入图片描述
RBAC模型的本质就是根据场景设计不同的角色,用角色来关联不同权限(菜单),最后不同的用户根据场景关联对应的角色即可。任何模型都是要根据不同的场景进行设计,不能简单设计,也不能过度设计。

2.2 spring security权限控制的几种方式

在spring security中,对接口的拦截或放行,主要有以下四种权限控制方式:

1.利用Ant表达式实现权限控制;
2.利用授权注解结合SpEL表达式实现权限控制;
3.利用过滤器注解实现权限控制;
4.利用动态权限实现权限控制。

2.2.1 Security Expression Operations

在Spring Security中,SecurityExpressionOperations接口定义了一系列方法用于用户权限的设置,如下所示:
在这里插入图片描述
这些方法的作用分别如下:
方法作用hasRole用户具备某个角色即可访问hasAnyRole用户具备多个角色中一个即可访问permitAll所有请求均允许访问denyAll所有请求都拒绝访问hasPermission用户具备某个权限即可访问isFullyAuthenticated判断是否用户名/密码登陆isRememberMe判断是否通过记住我功能登陆isAnonymous判断是否匿名登陆isAuthenticated允许认证通过用户访问hasAuthority判断是否拥有权限,类似于hasRolehasAnyAuthority判断是否拥有某一权限,类似于hasAnyRole
使用方法如下:

protectedvoidconfigure(HttpSecurity http)throwsException{//不通过Session获取SecurityContext
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()//登录接口允许匿名访问.authorizeRequests().antMatchers("/login/user").anonymous()//USER角色可以访问/user路径下所有请求.antMatchers("/user/**").hasRole("USER")//ADMIN角色可以访问/admin路径下所有请求.antMatchers("/admin/**").hasRole("ADMIN")//除上述接口,均需要授权访问.anyRequest().authenticated();// 关闭csrf
        http.csrf().disable();}

2.2.2 利用授权注解结合SpEL表达式实现权限控制

Spring Security提供了方法注解来进行权限控制,常用的授权注解如下:

@PreAuthorize:在方法执行前进行权限检查; 
@PostAuthorize:在方法执行后进行权限检查;
@Secured:类似于 @PreAuthorize。

使用方式如下:
1.首先利用@EnableGlobalMethodSecurity注解开启授权注解功能
在这里插入图片描述
2.在方法上开启注解进行权限控制
在这里插入图片描述
上述这种方式相对较为灵活,唯一的缺陷是代码耦合度较高。

2.2.3 利用过滤器注解实现权限控制

在Spring Security中还提供了另外的两个注解,即@PreFilter和@PostFilter,这两个注解可以对集合类型的参数或返回值进行过滤。使用@PreFilter和@PostFilter时,Spring Security将移除对应表达式结果为false的元素。
1.@PreFilter的用法
使用@PreFilter也可以对集合类型的参数进行过滤,当@PreFilter标注的方法内拥有多个集合类型的参数时,可以通过@PreFilter的filterTarget属性来指定当前是针对哪个参数进行过滤的;而filterObject是@PreFilter中的一个内置表达式,表示集合中的元素对象。下面案例中测试过滤奇数id:
在这里插入图片描述
测试结果如下:
在这里插入图片描述
2.@PostFilter的用法
@PostFilter注解主要是用于对集合类型的返回值进行过滤,filterObject是@PostFilter中的一个内置表达式,表示集合中的元素对象。
使用时可根据返回列表的指定条件来进行过滤返回结果,使用方式如下:

@Slf4j@ServicepublicclassMenuServiceImplimplementsMenuService{@ResourceprivateSysMenuDao sysMenuDao;@Override//根据状态来进行过滤,过滤出状态为0的菜单@PostFilter("filterObject.status.equals('0')")publicList<SysMenu>queryAllMenus(){List<SysMenu> sysMenus = sysMenuDao.selectAll();
        log.info("sysMenus:{}", JSON.toJSONString(sysMenus));return sysMenus;}}

数据集如下:
在这里插入图片描述
返回结果如下:
在这里插入图片描述

2.2.3 动态权限控制

Spring Security中的动态权限,主要是通过重写拦截器和决策器来进行实现,最简单的方法就是自定义一个Filter去完成权限判断。其实这里涉及到的代码,基本和Spring Security关系不大,主要是在传统的Filter进行实现,这里不再赘述。

2.3 spring security整合权限原理

本文将主要利用授权注解结合SpEL表达式实现权限控制,首先分析一下spring security权限认证流程。

在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从
SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。

鉴权认证通过时获取权限信息,存入缓存部分代码如下:
在这里插入图片描述
这样缓存中存储的用户信息就会携带权限信息,通过在方法上使用注解@PreAuthorize或@PostAuthorize来进行权限校验即可。该方法对应的sql如下:

<select id="selectMenusByUserId" resultType="string">
   SELECT DISTINCT sm.perms FROM `sys_user` u
    LEFT JOIN sys_user_role sur on u.id = sur.user_id
    LEFT JOIN sys_role sr ON sur.role_id = sr.id
    LEFT JOIN sys_role_menu srm ON srm.role_id = sur.role_id
    LEFT JOIN sys_menu sm ON srm.menu_id = sm.id
    WHERE u.id = #{id,jdbcType=BIGINT}</select>

上述sql主要是通过userId查询用户所拥有权限信息,具体表信息及解释可查看下文。

2.4 RBAC权限建表语句

CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
`path` varchar(200) DEFAULT NULL COMMENT '路由地址',
`component` varchar(255) DEFAULT NULL COMMENT '组件路径',
`visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
`status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`perms` varchar(255) DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`modify_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`))ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8;

CREATE TABLE `sys_role` (
`id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '角色名称',
`role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',
`status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`modify_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`update_by` bigint(200)DEFAULT NULL,
`remark` varchar(500) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`))ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8;

CREATE TABLE `sys_role_menu` (
`role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '权限id',PRIMARY KEY (`role_id`,`menu_id`))ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8;

CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`email` varchar(64) DEFAULT 'NULL' COMMENT '邮箱',
`mobile` varchar(32) DEFAULT 'NULL' COMMENT '手机号',
`sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`modify_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`))ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8;

CREATE TABLE `sys_user_role` (
`user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '权限id',PRIMARY KEY (`user_id`,`role_id`))ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8; 

INSERT INTO `zipkin`.`sys_user` (`id`, `user_name`, `password`, `status`, `create_time`, `modify_time`) VALUES ('1','admin', '$2a$10$GoLr2BQF77XaqSM9q3ETqu3fsbaIwOddz4YjvxoL8gGVph486OWmC', '1', '2022-05-2610:42:58', '2022-05-2610:42:58');

由于RBAC模型主要包含用户、角色、权限。因此本文主要设计了五张表:sys_user(用户表)、sys_menu(菜单表)、sys_role(权限表)、sys_role_menu(角色权限关联表)、sys_user_role(用户角色关联表)。

2.4 权限不足异常管理

在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给springSecurity即可。
如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给springSecurity即可。

@ComponentpublicclassAccessDeniedHandlerImplimplementsAccessDeniedHandler{@Overridepublicvoidhandle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException accessDeniedException)throwsIOException,ServletException{ResultData resultData =newResultData(HttpStatus.FORBIDDEN.value(),"用户权限不足");WebUtils.printString(response, JSON.toJSONString(resultData));}}
@ComponentpublicclassAuthenticationEntryPointImplimplementsAuthenticationEntryPoint{@Overridepublicvoidcommence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException)throwsIOException,ServletException{ResultData resultData =newResultData(HttpStatus.UNAUTHORIZED.value(),"用户认证失败");WebUtils.printString(response, JSON.toJSONString(resultData));}}

2.4 配置异常拦截

在这里插入图片描述

2.5 测试结果

1.用户认证失败(账号密码故意填错)
在这里插入图片描述
2.修改@PreAuthorize(“hasAuthority(‘sys:user:add1’)”)
实际用户拥有的权限为sys:user:add
在这里插入图片描述
3.修改@PreAuthorize(“hasAuthority(‘sys:user:add’)”)
用户拥有的权限也是sys:user:add,能够成功访问,得到如下结果:
在这里插入图片描述

3.小结

1.本文介绍了spring security中进行权限控制的四种方式,每种方式都有其对应的使用场景,应甄别使用;
2.RBAC权限模型是一种基础权限模型,较为灵活,应根据场景进行权限设计,避免设计不足或过度设计;
3.利用授权注解结合SpEL表达式实现权限控制是一种灵活的权限控制方式,主要缺陷在于代码耦合度较高。

4.参考文献

1.https://www.springcloud.cc/spring-security-zhcn.html#core-services-authentication-manager
2.https://zhuanlan.zhihu.com/p/349962352
3.https://www.csdn.net/tags/NtzaUgxsNTIxMTItYmxvZwO0O0OO0O0O.html

5.附录

https://gitee.com/Marinc/springboot-demos/tree/master/spring-security

标签: java spring 安全

本文转载自: https://blog.csdn.net/qq_33479841/article/details/125094762
版权归原作者 程可爱 所有, 如有侵权,请联系我们删除。

“SpringSecurity从入门到放弃之RBAC权限认证(二)”的评论:

还没有评论