一、SpringSecurity 动态授权

上篇文章我们介绍了SpringSecurity的动态认证,上篇文章就说了SpringSecurity 的两大主要功能就是认证和授权,既然认证以及学习了,那本篇文章一起学习了SpringSecurity 的动态授权。

上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122393435

二、SpringSecurity 授权

我们接着上篇文章的项目继续修改,上篇文章中有说到我们WebSecurityConfig配制类中的configure(HttpSecurity http)这个方法就是用来做授权的,现在就可以来体验一下了,比如我们修改以admin为开头的接口,权限或角色中需要有admin

 @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasAuthority("admin").antMatchers("/**").fullyAuthenticated().and().formLogin().permitAll().and().csrf().disable();}

下面使用admin用户访问admin/test接口:

报了403无权限的错误,因为我们设置了admin/**接口必须要有admin这个权限,可以看下上篇文章中写的UserService类:

这边直接给用户设定死了一个admin角色,这里就有个问题了权限和角色有什么区别,其实在SpringSecurity 中权限和角色都放在了一起,可以说概念上是一样的,但角色是以ROLE_开头的。

其中还需注意的是如果授权角色可以使用hasRole()hasAnyRole(),如果是授权权限则使用hasAuthority()hasAnyAuthority()

角色授权:授权代码需要加ROLE_前缀,controller上使用时不要加前缀。
权限授权:设置和使用时,名称保持一至即可。

所以可以修改UserService类:

在此请求接口:

现在就有权限访问了,但是写死肯定不是我们要的效果,所以此时可以将角色放在数据库中,通过查询数据库动态获取用户的角色。

下面就需要在数据库中创建role角色表:

CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT,`role` varchar(255) NOT NULL,`role_describe` varchar(255) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

角色肯定是和人有关系的,而且有时多对多的关系,所以根据关系模型我们要抽取出一个角色用户关系表:

CREATE TABLE `user_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`userid` int(11) NOT NULL,`roleid` int(11) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


对于角色的新增和关联用户,无非就是数据库的增删改,这里不做演示了,直接在创建好表可以在表中添加几条角色,并关联用户:


添加RoleEntity实体

@Data
@TableName("/role")
public class RoleEntity {private Long id;private String role;@TableField("role_describe")private String roleDescribe;
}

RoleMapper类,并写根据用户id查询全部角色的接口:

@Mapper
@Repository
public interface RoleMapper extends BaseMapper<RoleEntity> {@Select("SELECT r.id,r.role,r.role_describe FROM user_role u,role r where u.roleid = r.id AND u.userid = #{userId}")List<RoleEntity> getAllRoleByUserId(@Param("userId") Integer userId);
}

修改UserService类:

@Service
public class UserService implements UserDetailsService {@AutowiredUserMapper userMapper;@AutowiredRoleMapper roleMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, username);UserEntity userEntity = userMapper.selectOne(wrapper);if (userEntity == null) {throw new UsernameNotFoundException("用户不存在!");}List<GrantedAuthority> auths = roleMapper.getAllRoleByUserId(userEntity.getId()).stream().map(r -> new SimpleGrantedAuthority(r.getRole())).collect(Collectors.toList());userEntity.setRoles(auths);return userEntity;}public boolean register(String userName, String password) {UserEntity entity = new UserEntity();entity.setUsername(userName);entity.setPassword(new BCryptPasswordEncoder().encode(password));entity.setEnabled(true);entity.setLocked(false);return userMapper.insert(entity) > 0;}
}

下面就可以测试了,在浏览器再次访问上面的接口:

但是发现是403,原因是我们给admin设置的是权限admin,不是角色,数据库中存的是ROLE_admin,这里是想让大家对两者的区别更加深刻下,修改数据库为admin

重新启动再次访问:

已经可以访问了。上面大家应该对权限和角色有了一定的了解,下面对授权和授予角色的方法做下说明:

  • hasRole
    如果用户具备给定角色就允许访问,否则出现 403。给接口授权时无需写ROLE_开头,因为底层代码会自动添加与之进行匹配,用户添加角色时必须写ROLE_

  • hasAnyRole
    表示用户具备任何一个条件都可以访问。

  • hasAuthority
    如果当前的主体具有指定的权限,则返回 true,否则返回 false

  • hasAnyAuthority
    如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返true

现在我们已经了解怎么样给用户授权了,也知道怎么给接口赋予权限了,但是还是有个问题:

这个都在代码里面写死也不合适呀,其实这里有两种方案,一种是地址和角色的固定变化不大的场景下,可以在这里从数据库中读取出来通过HttpSecurity对象映射角色,但这种方案不太好在项目运行期间动态添加角色。还有一种方案就是实现FilterInvocationSecurityMetadataSource接口,在这里面根据当前访问的url返回该url所具有的全部角色。显然后者更为灵活,但每次访问一次接口都取获取全部的角色肯定性能有所损失。

下面分别实现下这两种情况:

三、数据库读取通过HttpSecurity授权

上面已经创建了role角色表,现在要做urlrole的关联,所以添加一个menu表用来存放url

CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT,`pattern` varchar(255) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

menurole也都是多对多的关系,所以也需要建一个menu_role关系表:

CREATE TABLE `menu_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`menu_id` int(11) NOT NULL,`role_id` int(11) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

还是在表中添加一些数据:


创建MeunEntity实体类:

@Data
@TableName("menu")
public class MeunEntity {@TableId(type = IdType.AUTO)private Integer id;private String pattern;
}

MeunMapper 继承BaseMapper

@Mapper
@Repository
public interface MeunMapper extends BaseMapper<MeunEntity> {
}

修改RoleMapper

@Mapper
@Repository
public interface RoleMapper extends BaseMapper<RoleEntity> {@Select("SELECT r.id,r.role,r.role_describe FROM user_role u,role r where u.roleid = r.id AND u.userid = #{userId}")List<RoleEntity> getAllRoleByUserId(@Param("userId") Integer userId);@Select("SELECT r.id,r.role,r.role_describe FROM menu_role m,role r where m.role_id = r.id AND m.menu_id = #{menuId}")List<RoleEntity> getAllRoleByMenuId(@Param("menuId") Integer menuId);
}

修改WebSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@AutowiredMeunMapper meunMapper;@AutowiredRoleMapper roleMapper;@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();List<MeunEntity> meunEntities = meunMapper.selectList(null);meunEntities.forEach(m -> {authorizeRequests.antMatchers(m.getPattern()).hasAnyAuthority(roleMapper.getAllRoleByMenuId(m.getId()).stream().map(RoleEntity::getRole).toArray(String[]::new));});authorizeRequests.antMatchers("/**").fullyAuthenticated().and().formLogin().permitAll().and().csrf().disable();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/register/**");}
}

重启项目,然后再次访问测试接口,已经实现和上面相同的效果:

四、通过FilterInvocationSecurityMetadataSource 动态角色

上面已经实现了第一种方案,下面继续实现第二中方案,下面创建一个类实现FilterInvocationSecurityMetadataSource 接口:

@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {@AutowiredMeunMapper meunMapper;@AutowiredRoleMapper roleMapper;//用来实现ant风格的Url匹配AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {//获取当前请求的UrlString requestUrl = ((FilterInvocation) object).getRequestUrl();List<MeunEntity> list = meunMapper.selectList(null);List<ConfigAttribute> roles = new ArrayList<>();list.forEach(m -> {if (antPathMatcher.match(m.getPattern(), requestUrl)) {List<ConfigAttribute> allRoleByMenuId = roleMapper.getAllRoleByMenuId(m.getId()).stream().map(r -> new SecurityConfig(r.getRole())).collect(Collectors.toList());roles.addAll(allRoleByMenuId);}});if (!roles.isEmpty()) {return roles;}return SecurityConfig.createList("ROLE_LOGIN");}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return true;}
}

还需创建一个CustomAccessDecisionManager用来实现AccessDecisionManager

@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication auth, Object object, Collection<ConfigAttribute> ca) throws AccessDeniedException, InsufficientAuthenticationException {for (ConfigAttribute configAttribute : ca) {//如果请求Url需要的角色是ROLE_LOGIN,说明当前的Url用户登录后即可访问if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && auth instanceof UsernamePasswordAuthenticationToken){ return;}Collection<? extends GrantedAuthority> auths = auth.getAuthorities(); //获取登录用户具有的角色for (GrantedAuthority grantedAuthority : auths) {if (configAttribute.getAttribute().equals(grantedAuthority.getAuthority())){return;}}}throw new AccessDeniedException("权限不足");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

修改WebSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@AutowiredCustomAccessDecisionManager customAccessDecisionManager;@AutowiredCustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);o.setAccessDecisionManager(customAccessDecisionManager);return o;}}).antMatchers("/**").fullyAuthenticated().and().formLogin().permitAll().and().csrf().disable();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/register/**");}@BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();String hierarchy = "ROLE_admin > ROLE_user > ROLE_common";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;}
}

再次测试上面的测试接口,可以发现也达到了相同的效果:

但是此时是动态角色的,我们可以创建一个新用户,给新用户一个新的角色,再给该角色赋予admin/**的权限。

创建用户adc

添加角色:

角色绑定用户:

角色绑定menu:


下面清楚浏览器的缓存,使用abc用户登录:


成功访问接口,说明动态角色权限已经生效了。


喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!

SpringSecurity - 用户动态授权 及 动态角色权限相关推荐

  1. Ant Design Pro v5 获取动态菜单与基于角色权限管理视频教程(33 个视频)

    Ant Design Pro v5 获取动态菜单与基于角色权限管理视频教程(33 个视频) 很多人问如何获取动态菜单的,所以在 antd prov 5 来讲一下角色管理的.从最基础的用户管理,角色管理 ...

  2. Linux用户的授权与文件的权限管理

    用户授权 ######1.查看主机名称:hostname ######2.权力下放###### 文件为/etc/sudoers 此文件可以用vim直接编辑,但是不提供语法检测 也可以使用visudo编 ...

  3. MySQL创建用户并授权

    MySQL创建用户并授权 创建用户 使用 命令创建 #使用 CREATECREATE USER 'testDatabase'@'%' IDENTIFIED BY '111111'; # CREATE ...

  4. 用户·角色·权限·表的设计

    设计一个灵活.通用.方便的权限管理系统. 在这个系统中,我们需要对系统的所有资源进行权限控制,那么系统中的资源包括哪些呢?我们可以把这些资源简单概括为静态资源(功能操作.数据列)和动态资源(数据),也 ...

  5. RBAC用户角色权限设计方案

    RBAC用户角色权限设计方案 转自http://www.cnblogs.com/zwq194/archive/2011/03/07/1974821.html RBAC(Role-Based Acces ...

  6. RBAC用户角色权限设计方案(转)

    RBAC用户角色权限设计方案 RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联.简单地说,一个用户拥有若干角色,每一个角色拥有若干权限 ...

  7. 扩展RBAC用户角色权限设计方案(转载)

    扩展RBAC用户角色权限设计方案 来源:https://www.cnblogs.com/zwq194/archive/2011/03/07/1974821.html https://blog.csdn ...

  8. easyui dialog 不执行页面js_Spring Security(六):前端菜单,角色权限页面的搭建

    文章回顾: Spring Security(一):整合JWT实现登录功能 Spring Security(二):获取用户权限菜单树 Spring Security(三):与Vue.js整合 Sprin ...

  9. RBAC角色权限设计

    RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联.简单地说,一个用户拥有若干角色,每一个角色拥有若干权限.这样,就构造成"用 ...

最新文章

  1. 清华系“AI帮”崛起,要驱动1500亿元产业规模
  2. mysql 包含非数字_mysql 正则表达式查询含有非数字和字符的记录
  3. Excel的VBA连接数据库方法
  4. 用Zend Encoder加密PHP文件和PHP 优化配置
  5. WordPress疑难问题以及解决方案汇总
  6. 自己动手实现权限控制(数据库表的设计)
  7. 哈尔滨有线电视频道表
  8. Keepalived原理详解
  9. 性能测试--jmeter中的察看结果树【7】
  10. 矩阵Jordan标准型过渡矩阵的求解
  11. windows清理_一个Windows系统下好用的内存清理工具
  12. 核心交换机、汇聚交换机、接入交换机功能详解
  13. 四、SolrCloud的安装
  14. 软件设计师备考笔记(炸薯条的视频)
  15. 关于微信小程序申请及个人信息和微信账号的问题
  16. 如何修改品牌电脑logo,让你电脑开机更个性
  17. 用笔在计算机制表格,东城附近学五笔打字,制表格计算机学校在哪里
  18. Android 百度地图位置显示和标记
  19. jdbc笔记(完整)
  20. Apache Benchmark(ab)使用

热门文章

  1. IT十年人生过客-十七-软通我来了
  2. Linux下开MC服务器
  3. R 语言 state 数据集的可视化
  4. ibm dsa生成html,IBMxSerial服务器故障诊断工具DSA
  5. 概念肆虐:凭什么让“IPv9”横行无忌?
  6. VisualStudio找不到Python.h
  7. Win10安装cuda11.1和cudnn8.0
  8. 【计算机网络】物理层 : 编码与调制 ( 基带信号 | 宽带信号 | 编码 | 调制 )
  9. java 代码实现各数据的正则校验
  10. python和nltk自然语言处理 脚本之家_想要入门自然语言处理,资料贫瘠,英语不好,大神推荐斯坦福的真的是搞不定,迷迷茫茫,不知从何下手?...