Spring Boot整合Apache Shiro权限认证框架(应用篇)
上一篇文章已经介绍了使用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等信息。
二、准备数据库
/*Navicat Premium Data TransferSource Server : localhostSource Server Type : MySQLSource Server Version : 80043 (8.0.43)Source Host : localhost:3306Source Schema : shiroTarget Server Type : MySQLTarget Server Version : 80043 (8.0.43)File Encoding : 65001Date: 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类。
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类。
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类。
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类。
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接口。
package cn.edu.sgu.www.shiro.mapper;import org.springframework.stereotype.Repository;/*** @author 沐雨橙风ιε* @version 1.0*/
@Repository
public interface RoleMapper {}
UserRoleMapper.java
在mapper包下创建UserRoleMapper接口。
package cn.edu.sgu.www.shiro.mapper;import org.springframework.stereotype.Repository;/*** @author 沐雨橙风ιε* @version 1.0*/
@Repository
public interface UserRoleMapper {}
PermissionMapper.java
在mapper包下创建PermissionMapper接口。
package cn.edu.sgu.www.shiro.mapper;import org.springframework.stereotype.Repository;/*** @author 沐雨橙风ιε* @version 1.0*/
@Repository
public interface PermissionMapper {}
RolePermissionMapper.java
在mapper包下创建RolePermissionMapper接口。
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作为参数。
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 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 jsbleft join user_role yhjsb on jsb.id = yhjsb.role_idleft join user yhb on yhjsb.user_id = yhb.idwhere yhb.id = #{userId}</select>
</mapper>
application.yml
设置日志级别为debug,控制台将会打印执行的SQL语句。
logging:level: # 日志级别设置springfox: errorcn.edu.sgu.www.shiro: debugmybatis:mapper-locations: classpath:mapper/*Mapper.xml # mybatis的mapper.xml文件的位置
2、查询角色的权限
RolePermissionMapper.java
增加一个selectRolePermissions()方法,接收角色id作为参数。
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 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 qxbleft join role_permission jsqxb on qxb.id = jsqxb.permission_idleft join role jsb on jsqxb.role_id = jsb.idwhere jsb.id = #{roleId}</select>
</mapper>
3、设置角色的权限列表
UsernameRealm.java
注入UserRoleMapper和RolePermissionMapper两个依赖。
- 根据用户ID查询用户的角色列表
- 遍历角色列表,根据角色ID查询角色的权限列表
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;@Autowiredpublic UsernameRealm(UserMapper userMapper,UserRoleMapper userRoleMapper,RolePermissionMapper rolePermissionMapper) {this.userMapper = userMapper;this.userRoleMapper = userRoleMapper;this.rolePermissionMapper = rolePermissionMapper;}/*** 获取认证信息*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {// 获取登录时提交的tokenUsernamePasswordToken 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);}/*** 获取授权信息*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 得到用户IDUser 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()方法,查询所有权限。
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 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 permissionwhere parent_id is not null</select>
</mapper>
ShiroConfig.java
注入PermissionMapper的依赖,查询所有权限,根据是否匿名接口交给perms或anno过滤器处理。
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;@Autowiredpublic UsernameRealm(UserMapper userMapper,UserRoleMapper userRoleMapper,RolePermissionMapper rolePermissionMapper) {this.userMapper = userMapper;this.userRoleMapper = userRoleMapper;this.rolePermissionMapper = rolePermissionMapper;}/*** 获取认证信息*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {// 获取登录时提交的tokenUsernamePasswordToken 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);}/*** 获取授权信息*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 得到用户IDUser 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