上一篇文章已经介绍了使用Apache Shiro权限认证框架实现简单的认证和授权功能。
Spring Boot整合Apache Shiro权限认证框架(基础篇)https://blog.csdn.net/2501_92713943/article/details/152612858
这篇文章的主要内容是Apache Shiro的更深层次的应用,主要为了解决上篇文章的权限硬编码问题
一、RBAC介绍
权限访问控制通常会使用RBAC(Role-Based Access Control,基于角色的访问控制)模型。
实现访问控制最少需要5张表:
- 用户表
- 角色表
- 权限表
- 用户-角色表
- 角色-权限表
权限是分配给角色的,通过用户-角色表关联到用户,从而实现用户权限的访问控制。
权限对应的就是项目中的接口资源信息,包括资源名称、资源URL等信息。
二、准备数据库
sql
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80043 (8.0.43)
Source Host : localhost:3306
Source Schema : shiro
Target Server Type : MySQL
Target Server Version : 80043 (8.0.43)
File Encoding : 65001
Date: 07/10/2025 19:57:04
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '接口路径',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限名称',
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限值(shiro的权限通配符)',
`parent_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级权限ID',
`anonymity` tinyint UNSIGNED NOT NULL COMMENT '是否允许匿名访问(0-不允许;1-允许)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '权限表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES ('UserController', '/user', '用户管理', 'user:*', NULL, 0);
INSERT INTO `permission` VALUES ('UserController_delete', '/user/delete', '删除用户', 'user:delete', 'UserController', 0);
INSERT INTO `permission` VALUES ('UserController_login', '/user/login', '用户登录', 'user:login', 'UserController', 1);
INSERT INTO `permission` VALUES ('UserController_update', '/user/update', '修改用户', 'user:update', 'UserController', 0);
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名称',
`note` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色说明',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('system-admin', '超级管理员', '最高权限,拥有系统所有权限');
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`role_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色id,数据来源于role表的主键',
`permission_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限id,数据来源于permission表的主键',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色-权限表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 'system-admin', 'UserController_login');
INSERT INTO `role_permission` VALUES (2, 'system-admin', 'UserController_delete');
INSERT INTO `role_permission` VALUES (3, 'system-admin', 'UserController_update');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录密码',
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号',
`gender` tinyint UNSIGNED NOT NULL COMMENT '性别(1-男;2-女)',
`disabled` tinyint UNSIGNED NOT NULL COMMENT '封禁状态(0-正常;1-封禁)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('20241215', 'mumu', 'mhxy1218', '18888888888', 2, 0);
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`role_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色id,数据来源于role表的主键',
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户id,数据来源于user表的主键',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户-角色关系表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 'system-admin', '20241215');
SET FOREIGN_KEY_CHECKS = 1;
三、创建实体类
Role.java
在entity包下创建Role类。
java
package cn.edu.sgu.www.shiro.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 角色
* @author 沐雨橙风ιε
* @version 1.0
*/
@Data
public class Role implements Serializable {
private static final long serialVersionUID = 18L;
private String id;
/**
* 角色名称
*/
private String name;
/**
* 角色说明
*/
private String note;
}
UserRole.java
在entity包下创建UserRole类。
java
package cn.edu.sgu.www.shiro.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 用户-角色
* @author 沐雨橙风ιε
* @version 1.0
*/
@Data
public class UserRole implements Serializable {
private static final long serialVersionUID = 18L;
private Integer id;
/**
* 用户ID
*/
private String userId;
/**
* 角色ID
*/
private String roleId;
}
Permission.java
在entity包下创建Permission类。
java
package cn.edu.sgu.www.shiro.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 权限
* @author 沐雨橙风ιε
* @version 1.0
*/
@Data
public class Permission implements Serializable {
private static final long serialVersionUID = 18L;
private String id;
/**
* 接口路径
*/
private String url;
/**
* 权限名称
*/
private String name;
/**
* 权限值
*/
private String value;
/**
* 父级权限ID
*/
private String parentId;
/**
* 是否允许匿名访问
* 0-不允许;1-允许
*/
private Integer anonymity;
}
RolePermission.java
在entity包下创建RolePermission类。
java
package cn.edu.sgu.www.shiro.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 角色-权限
* @author 沐雨橙风ιε
* @version 1.0
*/
@Data
public class RolePermission implements Serializable {
private static final long serialVersionUID = 18L;
private Integer id;
/**
* 角色ID
*/
private String roleId;
/**
* 权限ID
*/
private String permissionId;
}
四、创建持久层接口
RoleMapper.java
在mapper包下创建RoleMapper接口。
java
package cn.edu.sgu.www.shiro.mapper;
import org.springframework.stereotype.Repository;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface RoleMapper {
}
UserRoleMapper.java
在mapper包下创建UserRoleMapper接口。
java
package cn.edu.sgu.www.shiro.mapper;
import org.springframework.stereotype.Repository;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface UserRoleMapper {
}
PermissionMapper.java
在mapper包下创建PermissionMapper接口。
java
package cn.edu.sgu.www.shiro.mapper;
import org.springframework.stereotype.Repository;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface PermissionMapper {
}
RolePermissionMapper.java
在mapper包下创建RolePermissionMapper接口。
java
package cn.edu.sgu.www.shiro.mapper;
import org.springframework.stereotype.Repository;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface RolePermissionMapper {
}
五、设置动态权限
因为权限是(通过角色-权限表)直接和角色绑定的,所以用户的权限就是用户拥有的(从用户-角色表查询到的)所有角色的权限的并集。
因此,用户的权限分两步查询:
- 查询用户的角色
- 查询角色的权限
1、查询用户的角色
UserRoleMapper.java
增加一个selectUserRoles()方法,接收用户id作为参数。
java
package cn.edu.sgu.www.shiro.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface UserRoleMapper {
/**
* 通过用户ID查询角色列表
* @param userId 用户ID
* @return 用户的角色列表
*/
List<String> selectUserRoles(@Param("userId") Object userId);
}
UserRoleMapper.xml
在src/main/resources目录下创建mapper目录,在mapper目录下创建UserRoleMapper.xml文件。
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.edu.sgu.www.shiro.mapper.UserRoleMapper">
<select id="selectUserRoles" resultType="String">
select jsb.id from role jsb
left join user_role yhjsb on jsb.id = yhjsb.role_id
left join user yhb on yhjsb.user_id = yhb.id
where yhb.id = #{userId}
</select>
</mapper>
application.yml
设置日志级别为debug,控制台将会打印执行的SQL语句。
java
logging:
level: # 日志级别设置
springfox: error
cn.edu.sgu.www.shiro: debug
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml # mybatis的mapper.xml文件的位置
2、查询角色的权限
RolePermissionMapper.java
增加一个selectRolePermissions()方法,接收角色id作为参数。
java
package cn.edu.sgu.www.shiro.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface RolePermissionMapper {
/**
* 通过角色ID查询权限列表
* @param roleId 角色ID
* @return 角色的权限列表
*/
List<String> selectRolePermissions(@Param("roleId") String roleId);
}
RolePermissionMapper.xml
在src/main/resources/mapper目录下创建RolePermissionMapper.xml文件。
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.edu.sgu.www.shiro.mapper.RolePermissionMapper">
<select id="selectRolePermissions" resultType="java.lang.String">
select qxb.value from permission qxb
left join role_permission jsqxb on qxb.id = jsqxb.permission_id
left join role jsb on jsqxb.role_id = jsb.id
where jsb.id = #{roleId}
</select>
</mapper>
3、设置角色的权限列表
UsernameRealm.java
注入UserRoleMapper和RolePermissionMapper两个依赖。
- 根据用户ID查询用户的角色列表
- 遍历角色列表,根据角色ID查询角色的权限列表
java
package cn.edu.sgu.www.shiro.realm;
import cn.edu.sgu.www.shiro.consts.ErrorMessages;
import cn.edu.sgu.www.shiro.entity.User;
import cn.edu.sgu.www.shiro.mapper.RolePermissionMapper;
import cn.edu.sgu.www.shiro.mapper.UserMapper;
import cn.edu.sgu.www.shiro.mapper.UserRoleMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Component
public class UsernameRealm extends AuthorizingRealm {
private final UserMapper userMapper;
private final UserRoleMapper userRoleMapper;
private final RolePermissionMapper rolePermissionMapper;
@Autowired
public UsernameRealm(
UserMapper userMapper,
UserRoleMapper userRoleMapper,
RolePermissionMapper rolePermissionMapper) {
this.userMapper = userMapper;
this.userRoleMapper = userRoleMapper;
this.rolePermissionMapper = rolePermissionMapper;
}
/**
* 获取认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取登录时提交的token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 得到用户名
String username = token.getUsername();
// 根据用户名查询用户信息
User user = userMapper.selectByUsername(username);
// 查询结果为空,则说明用户不存在
if (user == null) {
throw new AuthenticationException(ErrorMessages.loginFail);
} else if (user.isDisabled()) { // 账号被封禁,抛出异常
throw new AuthenticationException("登录失败,账号状态异常!");
}
return new SimpleAuthenticationInfo(user, user.getPassword(), username);
}
/**
* 获取授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 得到用户ID
User user = (User) principals.getPrimaryPrincipal();
String userId = user.getId();
// 查询用户的角色列表
List<String> roleList = userRoleMapper.selectUserRoles(userId);
if (!roleList.isEmpty()) {
authorizationInfo.setRoles(new HashSet<>(roleList));
Set<String> stringPermissions = new HashSet<>();
for (String roleId : roleList) {
// 查询角色的权限列表
List<String> permissions = rolePermissionMapper.selectRolePermissions(roleId);
if (!permissions.isEmpty()) {
stringPermissions.addAll(permissions);
}
}
if (!stringPermissions.isEmpty()) {
authorizationInfo.setStringPermissions(stringPermissions);
}
}
return authorizationInfo;
}
}
六、配置拦截规则
PermissionMapper.java
增加一个selectPermissions()方法,查询所有权限。
java
package cn.edu.sgu.www.shiro.mapper;
import cn.edu.sgu.www.shiro.entity.Permission;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Repository
public interface PermissionMapper {
/**
* 查询所有权限
*/
List<Permission> selectPermissions();
}
PermissionMapper.xml
在src/main/resources/mapper目录下创建PermissionMapper.xml文件。
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.edu.sgu.www.shiro.mapper.PermissionMapper">
<select id="selectPermissions" resultType="cn.edu.sgu.www.shiro.entity.Permission">
select * from permission
where parent_id is not null
</select>
</mapper>
ShiroConfig.java
注入PermissionMapper的依赖,查询所有权限,根据是否匿名接口交给perms或anno过滤器处理。
java
package cn.edu.sgu.www.shiro.realm;
import cn.edu.sgu.www.shiro.consts.ErrorMessages;
import cn.edu.sgu.www.shiro.entity.User;
import cn.edu.sgu.www.shiro.mapper.RolePermissionMapper;
import cn.edu.sgu.www.shiro.mapper.UserMapper;
import cn.edu.sgu.www.shiro.mapper.UserRoleMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author 沐雨橙风ιε
* @version 1.0
*/
@Component
public class UsernameRealm extends AuthorizingRealm {
private final UserMapper userMapper;
private final UserRoleMapper userRoleMapper;
private final RolePermissionMapper rolePermissionMapper;
@Autowired
public UsernameRealm(
UserMapper userMapper,
UserRoleMapper userRoleMapper,
RolePermissionMapper rolePermissionMapper) {
this.userMapper = userMapper;
this.userRoleMapper = userRoleMapper;
this.rolePermissionMapper = rolePermissionMapper;
}
/**
* 获取认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取登录时提交的token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 得到用户名
String username = token.getUsername();
// 根据用户名查询用户信息
User user = userMapper.selectByUsername(username);
// 查询结果为空,则说明用户不存在
if (user == null) {
throw new AuthenticationException(ErrorMessages.loginFail);
} else if (user.isDisabled()) { // 账号被封禁,抛出异常
throw new AuthenticationException("登录失败,账号状态异常!");
}
return new SimpleAuthenticationInfo(user, user.getPassword(), username);
}
/**
* 获取授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 得到用户ID
User user = (User) principals.getPrimaryPrincipal();
String userId = user.getId();
// 查询用户的角色列表
List<String> roleList = userRoleMapper.selectUserRoles(userId);
if (!roleList.isEmpty()) {
authorizationInfo.setRoles(new HashSet<>(roleList));
Set<String> stringPermissions = new HashSet<>();
for (String roleId : roleList) {
// 查询角色的权限列表
List<String> permissions = rolePermissionMapper.selectRolePermissions(roleId);
if (!permissions.isEmpty()) {
stringPermissions.addAll(permissions);
}
}
if (!stringPermissions.isEmpty()) {
authorizationInfo.setStringPermissions(stringPermissions);
}
}
return authorizationInfo;
}
}
好了,本章关于Apache Shiro的内容就分享到这里了。
如果这篇文章对你有所帮助,不要忘了点赞+收藏哦~
文章的项目已经上传到Git仓库,需要的可以自行拉取~
Spring Boot整合Apache Shiro案例项目https://gitee.com/muyu-chengfeng/springboot-shiro-v2.git