在写这篇博客之前,我在网上找了很多资料,关于shiro的动态权限管理,网上的资料要么写的不是很详细,要么就是写的比较复杂,对于初学者来说极其不友好,于是我就琢磨出了一个相对比较简单的实现方法,希望能对大家有所帮助,如果有什么不足之处,还请在评论区留言,毕竟我技术一般,考虑不周实属正常。
目录
说明
动态权限是程序运行期间,对用户的权限进行更改而不需要修改源码。在shiro中,实现动态权限只需要实现一个自定义的过滤器即可,这里使用的方法是继承PathMatchingFilter
。
需要注意的是,任何的动态权限都会损耗服务器的资源,因为所谓的动态权限就是根据数据库的变化,实时的将情况反应到客户端,即每次请求都需要对数据库发送请求,因为需要的是实时的数据,所以没办法使用Redis解决这个问题,因为即使存储到Redis中,Redis也需要去向数据库发送请求来更新数据,甚至因为请求经过了Redis,还会增加Redis的损耗。
数据库
用户表
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'aa', '894b3913a4a13b25dc6186d11835c209', 'abc');
INSERT INTO `user` VALUES (2, 'bb', '894b3913a4a13b25dc6186d11835c209', 'abc');
INSERT INTO `user` VALUES (3, 'cc', '894b3913a4a13b25dc6186d11835c209', 'abc');
角色表
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'user');
INSERT INTO `role` VALUES (2, 'admin');
INSERT INTO `role` VALUES (3, 'guest');
用户角色表
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NULL DEFAULT NULL,
`role_id` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 2, 2);
INSERT INTO `user_role` VALUES (3, 3, 3);
权限表
-- ----------------------------
-- Table structure for access
-- ----------------------------
DROP TABLE IF EXISTS `access`;
CREATE TABLE `access` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of access
-- ----------------------------
INSERT INTO `access` VALUES (1, 'v1', '/v1');
INSERT INTO `access` VALUES (2, 'v2', '/v2');
INSERT INTO `access` VALUES (3, 'v3', '/v3');
角色权限表
-- ----------------------------
-- Table structure for role_access
-- ----------------------------
DROP TABLE IF EXISTS `role_access`;
CREATE TABLE `role_access` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int NULL DEFAULT NULL,
`access_id` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of role_access
-- ----------------------------
INSERT INTO `role_access` VALUES (1, 1, 1);
INSERT INTO `role_access` VALUES (2, 2, 2);
INSERT INTO `role_access` VALUES (3, 3, 3);
代码
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入shrio-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--导入这个依赖的目的是使用里面一个随机生成字符串的工具类,这个随机字符串可以当作我们的盐值-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
yml配置
spring:
datasource:
password: 123456
username: root
url: jdbc:mysql:///rbac?serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
thymeleaf:
cache: false
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
pojo层
实体类依据表进行创建,使用Lombok的@Data
注解,以下是一个具体的例子,剩余四个实体类大家可以照此例进行创建。
package com.th.rbac.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.security.core.userdetails.UserDetails;
@Data
public class User{
private long id;
private String name;
private String password;
}
dao层
由于使用的是Mybatis-plus,所以这里只需要继承BaseMapper
就可以了,下面是一个具体的例子:
package com.th.rbac.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.th.rbac.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
service层
由于只是进行简单的测试,所以并没有创建接口,而是直接写的实现类,需要继承mybatisplus中的ServiceImpl
,示例如下:
package com.th.rbac.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.th.rbac.mapper.UserMapper;
import com.th.rbac.pojo.User;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> {
}
controller层
package com.th.shiro.controller;
import com.th.shiro.pojo.User;
import com.th.shiro.pojo.UserRole;
import com.th.shiro.service.UserRoleService;
import com.th.shiro.service.UserService;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
public class SecurityController {
@Resource
private UserService userService;
@Resource
private UserRoleService userRoleService;
//跳转到登录页面
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
//跳转到注册页面
@RequestMapping("/toRegist")
public String toRegist() {
return "regist";
}
//登录
@RequestMapping("/login")
public String login(String name, String password, Model model) {
try {
//使用账号密码生成token
UsernamePasswordToken token = new UsernamePasswordToken(name,password);
SecurityUtils.getSubject().login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
model.addAttribute("error","用户名错误~");
return "error";
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
model.addAttribute("error","密码错误~");
return "error";
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("error","系统异常");
return "error";
}
return "index";
}
//注册
@RequestMapping("/regist")
public String regist(User user) {
//生成随机的字符串,即我们要用到的盐值
String salt = RandomStringUtils.randomAlphanumeric(6);
//使用MD5加密
Md5Hash password = new Md5Hash(user.getPassword(), salt, 1024);
//将加密后的密码和盐值放入到实体类中
user.setPassword(String.valueOf(password));
user.setSalt(salt);
//新增用户
userService.save(user);
/**
* 接下来需要给用户赋予一个角色才行,这就需要拿到该用户的id
* 我这里是直接根据用户名去查询的
* 如果是mybatis,这里应该会有一个反射机制,在新增的时候,实体类的id值就已经有了
* 但是mybatis-plus在这里似乎并没有使用到这个反射机制
* 总之user对象的id是0,懒得去研究源码了,直接简单粗暴的查询
*/
Map<String, Object> map = new HashMap<>();
map.put("name", user.getName());
List<User> users = userService.listByMap(map);
User user1 = users.get(0);
//设置好值之后就可以新增了
UserRole userRole = new UserRole();
userRole.setUserId(user1.getId());
userRole.setRoleId(1);
userRoleService.save(userRole);
return "login";
}
//退出登录
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
//没有权限
@RequestMapping("/unAuth")
@ResponseBody
public String unAuth(){
return "没有权限哦~";
}
//返回v1
@RequestMapping("/v1")
@ResponseBody
public String v1() {
return "v1";
}
//返回v2
@RequestMapping("/v2")
@ResponseBody
public String v2() {
return "v2";
}
//返回v3
@RequestMapping("/v3")
@ResponseBody
public String v3() {
return "v3";
}
}
首页
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/v1">v1</a><br/>
<a href="/v2">v2</a><br/>
<a href="/v3">v3</a><br/>
<a href="/toRegist">注册</a><br/>
<a href="/logout">退出</a><br/>
</body>
</html>
登录页
与spring security不同的是,shiro并没有自带一个登录页面,这需要我们自己去写
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
账号:<input type="text" name="name"/><br/>
密码:<input type="text" name="password"/><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
注册页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/regist" method="post">
账号:<input type="text" name="name"/><br/>
密码:<input type="text" name="password"/><br/>
<input type="submit" value="注册">
</form>
</body>
</html>
错误页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${error}"></h1>
</body>
</html>
启动类
package com.th.shiro;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.th.shiro.mapper")
public class ShiroApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroApplication.class, args);
}
}
至此,这个案例的基本框架就搭建好了,但是需要注意的是,这个时候是运行不了的,运行了也会报错,因为shiro是第三方框架,而不是spring旗下的,所以整合起来难免会有些问题。
接下来我们将shiro整合到spring boot中,并且实现shiro的动态权限管理,只需要编写三个类即可:
UserRealm
:这个类用来连接数据库,需要继承AuthorizingRealm
类ShiroConfig
:这个类是shiro的配置类,用它来配置一些东西ShiroFilter
:这个类是最关键的,它需要实现shiro的PathMatchingFilter
类中的onPreHandle
方法,进行动态权限管理
UserRealm
package com.th.shiro.mapper;
import com.th.shiro.pojo.*;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserRealm extends AuthorizingRealm {
@Resource
private UserMapper userMapper;
/**
* 授权
* 这里不需要将用户的权限和角色放入到SimpleAuthorizationInfo中
* 就算写了,也没用,因为动态代理是需要从数据库去查询的
* 而这里仅仅只会在用户登录的时候执行一次,即在用户登录的时候就把权限和角色定死了
* 即使在数据库修改了用户的权限和角色,只要用户没有退出登录,那么用户就还保有角色和权限
* 直到用户重新登陆的时候才会从数据库拿到被修改后的角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String name = (String)authenticationToken.getPrincipal();
//从数据库查询用户信息
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", name);
List<User> users = userMapper.selectByMap(userMap);
if (users.size() == 0) {
throw new UnknownAccountException("没有此账号");
}else{
User user = users.get(0);
return new SimpleAuthenticationInfo(name, //用户名
user.getPassword(), //加密后的密码
ByteSource.Util.bytes(user.getSalt()), //随机盐
getName()); //当前realm的名称
}
}
}
ShiroFilter
package com.th.shiro.filter;
import com.th.shiro.mapper.*;
import com.th.shiro.pojo.*;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ShiroFilter extends PathMatchingFilter {
@Resource
private UserMapper userMapper;
@Resource
private RoleMapper roleMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private AccessMapper accessMapper;
@Resource
private RoleAccessMapper roleAccessMapper;
/**
* 这个方法会在用户发送请求时执行
* 如果有兴趣,可以在AccessControlFilter类中的onPreHandle方法打一个断点调试
* @param request 请求
* @param response 响应
* @param mappedValue 映射值,映射的是访问该路径所需要的权限
* @return
* @throws Exception
*/
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = SecurityUtils.getSubject();
// 如果没有登录,就跳转到登录页面
if (!subject.isAuthenticated()) {
WebUtils.issueRedirect(request, response, "/toLogin");
return false;
}
/**
* 接下来就是从数据库查询用户的权限,与访问该路径需要的权限进行对比
* 如果用户没有该权限,则无法访问,如果有,则可以访问
* 因为使用的是mybatis-pulse,所以这里的查询代码会显得繁杂
*/
String name = subject.getPrincipal().toString();
//从数据库查询用户信息
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", name);
List<User> users = userMapper.selectByMap(userMap);
User user = users.get(0);
//查询角色关系表
Map<String, Object> map = new HashMap<>();
map.put("user_id", user.getId());
List<UserRole> userRoles = userRoleMapper.selectByMap(map);
//查询角色信息
List<Role> roleList = new ArrayList<>();
for (UserRole userRole : userRoles) {
Role role = roleMapper.selectById(userRole.getRoleId());
roleList.add(role);
}
//查询角色权限表
List<List<RoleAccess>> roleAccessList = new ArrayList<>();
for (Role role : roleList) {
Map<String, Object> roleAccessMap = new HashMap<>();
roleAccessMap.put("role_id",role.getId());
roleAccessList.add(roleAccessMapper.selectByMap(roleAccessMap));
}
//查询权限信息
List<Access> accessList = new ArrayList<>();
for (List<RoleAccess> roleAccesses : roleAccessList) {
for (RoleAccess roleAccess : roleAccesses) {
Access access = accessMapper.selectById(roleAccess.getAccessId());
accessList.add(access);
}
}
//查询到用户的权限后,就可以开始进行判断了
String[] perms = (String[]) mappedValue;
for (String perm : perms) {
for (Access access : accessList) {
if (perm.equals(access.getName())) {
return true;
}
}
}
//如果用户没有权限,则跳转到无权限页面
WebUtils.issueRedirect(request, response, "/unAuth");
return false;
}
}
ShiroConfig
package com.th.shiro.config;
import com.th.shiro.filter.ShiroFilter;
import com.th.shiro.mapper.AccessMapper;
import com.th.shiro.mapper.UserRealm;
import com.th.shiro.pojo.Access;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Resource
private AccessMapper accessMapper;
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给ShiroFilter配置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//从数据库中读取权限
List<Access> accesses = accessMapper.selectList(null);
//自定义拦截器
Map<String, Filter> filtersMap = new LinkedHashMap();
filtersMap.put("requestURL", shiroFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
//配置系统受限资源和公共资源,可以直接在数据库中读取
Map<String, String> map = new HashMap<String, String>();
for (Access access : accesses) {
/**
* 这里需要使用到自己的自定义拦截器
* 由于它是一个字符串,所以这里使用StringBuffer进行连接
* 字符串的规则:过滤器[权限]
* 一般情况下,这个字符串是这样的:roles[admin],或者perms[v1]
* 这里就是用到了shiro自带的过滤器,roles和perms,前者是角色过滤器,后者是权限过滤器
* 而使用我们自己的过滤器,是这样的requestURL[v1]
* 如果你的过滤器名称不叫requestURL,请写上自己的名称
* 另外,这里值得一提的是,在ShiroFilter类的onPreHandle方法中
* mappedValue属性正是中括号中的字符串
* 如果你仅仅只是写上requestURL,而没有在后方加上[权限],则mappedValue属性是null
*/
StringBuffer perms = new StringBuffer();
perms.append("requestURL[");
perms.append(access.getName());
perms.append("]");
map.put(access.getUrl(), perms.toString());
}
// 设置认证界面路径
shiroFilterFactoryBean.setLoginUrl("/toLogin");
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//创建自定义Realm
@Bean
public Realm getRealm() {
//给自定义的realm设置密码匹配器
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(md5HashedCredentialsMatcher());
return userRealm;
}
//密码匹配器
@Bean
public HashedCredentialsMatcher md5HashedCredentialsMatcher(){
//获取hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置hash凭证匹配器使用的算法(MD5)
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//如果使用散列(加密次数),则需要设置散列的次数:1024次,与注册时的加密次数是一致的
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
//自定义的拦截器
@Bean
public ShiroFilter shiroFilter() {
return new ShiroFilter();
}
/**
* 如果不写这个配置,那么在启动程序的时候,会出现jdk动态代理与CGLIB代理混乱的问题
* 而这个配置的意思,就是指定代理为CGLIB代理
* 至于这两种代理,可以去百度,这里就不进行赘述了
* 这是shiro和spring boot整合所产生的问题
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
}
测试
启动程序,访问localhost:8080端口
点击v1
,会跳到登录页面
在数据库中,我给了三个默认的用户,密码是123,当然也可以自己注册一个用户,回到首页,点击注册,进入到注册页面
注册成功后会跳到登录页,登录即可,登录之后是只能访问v1
的,因为新用户默认只有进入v1的权限,如下:
赋予新角色
接下来进行动态权限的管理,比如说要让新用户拥有访问v2的权限,这里可以给新用户赋予新的角色,修改数据库,在用户角色关系表中增加一条数据,如下:
INSERT INTO `user_role` VALUES (5, 4, 2);
接下来不需要重新登录,也不需要重启项目,直接访问v2
,是可以访问成功的
赋予角色权限
另外也可以动态的修改角色所拥有的权限,比如说我们希望user
角色能够访问v3,那么只需要在角色权限关系表中新增一条数据即可
INSERT INTO `role_access` VALUES (4, 1, 3);
直接访问v3,如下:
如果要删除用户的角色,或者删除角色的权限,只需要删除数据库对应的记录即可
小结
至此关于shiro的动态权限管理就结束了,需要注意的是,如果我们修改了权限规则,例如将/v3
路径的权限改为v1
,则需要重启程序才行,因为权限规则只会在程序运行的时候,从数据库查询一次,之后就不会再次从数据库查询了,当然,这种缺陷是可以解决的,例如写个定时器,定时从数据库中查询,或者重写shiro的方法等等,有兴趣的可以自己去试试。
另外,shiro是可以和Spring Security进行对比学习的,有兴趣的话,可以去看看我的另一篇博客:Spring Security
另外,上边的代码都发布到gitee上面去了有兴趣可以拉取下来看看:https://gitee.com/thlyj/shiro