1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > SpringSecurity - 用户动态授权 及 动态角色权限

SpringSecurity - 用户动态授权 及 动态角色权限

时间:2023-08-27 04:08:33

相关推荐

SpringSecurity - 用户动态授权 及 动态角色权限

一、SpringSecurity 动态授权

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

上篇文章地址:/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@Repositorypublic 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类:

@Servicepublic 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@Repositorypublic interface MeunMapper extends BaseMapper<MeunEntity> {}

修改RoleMapper

@Mapper@Repositorypublic 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接口:

@Componentpublic 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

@Componentpublic 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用户登录:

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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。