系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
Shiro之授权
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
在当今的应用开发中,安全是至关重要的一环。而 Shiro 作为一个强大而灵活的安全框架,为我们提供了一种简单而有效的方式来管理应用程序的授权。
在这篇博客中,我将带领大家深入了解 Shiro 的授权功能,以及如何使用 Shiro 来保护我们的应用程序。
通过实际的案例和代码示例,我将向你展示如何使用 Shiro 来控制对资源的访问,以及如何实现细粒度的权限控制。
无论你是刚刚开始接触 Shiro,还是已经有一定经验的开发者,这篇博客都将为你提供宝贵的知识和实用的技巧。让我们一起探索 Shiro 的授权世界,为我们的应用程序增添一层强大的安全保护。
提示:以下是本篇文章正文内容,下面案例可供参考
一、Shiro之授权
当用户认证后就可以正常访问,但并不是所有的资源都可以访问,假如你不是VIP,那么你就不能访问VIP才能访问的资源。如果你想访问VIP的资源,你必须拥有访问VIP资源的权限,这就是授权的应用范围。
权限表的设计
CREATE TABLE `role` (
`rid` int(11) NOT NULL AUTO_INCREMENT,
`roleName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`roleDesc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`rid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `role` VALUES (1, '总经理', '管理整个公司');
INSERT INTO `role` VALUES (2, '股东', '参与公司决策');
INSERT INTO `role` VALUES (3, '财务', '管理公司资产');
CREATE TABLE `permission` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`permissionName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `permission` VALUES (1, '查询报表', '/reportform/find');
INSERT INTO `permission` VALUES (2, '查询工资', '/salary/find');
INSERT INTO `permission` VALUES (3, '查询税务', '/tax/find');
CREATE TABLE `users_role` (
`uid` int(255) NOT NULL,
`rid` int(11) NOT NULL,
PRIMARY KEY (`uid`, `rid`) USING BTREE,
INDEX `rid`(`rid`) USING BTREE,
CONSTRAINT `users_role_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `users` (`uid`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `users_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`rid`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `users_role` VALUES (1, 2);
INSERT INTO `users_role` VALUES (1, 3);
CREATE TABLE `role_permission` (
`rid` int(11) NOT NULL,
`pid` int(11) NOT NULL,
PRIMARY KEY (`rid`, `pid`) USING BTREE,
INDEX `pid`(`pid`) USING BTREE,
CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `role` (`rid`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`pid`) REFERENCES `permission` (`pid`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (2, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (3, 2);
INSERT INTO `role_permission` VALUES (1, 3);
INSERT INTO `role_permission` VALUES (2, 3);
数据库查询权限
1.编写用户、角色、权限实体类
@Data
public class Users implements Serializable {
private Integer uid;
private String username;
private String password;
private String salt;
}
// 角色
@Data
public class Role implements Serializable{
private Integer rid;
private String roleName;
private String roleDesc;
}
// 权限
@Data
public class Permission implements Serializable{
private Integer pid;
private String permissionName;
private String url;
}
2.修改UsersMapper接口
// 根据用户id查询权限
List<Permission> findPermissionById(Integer id);
3.在resources目录中编写UsersMapper的映射文件
<?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="com.itbaizhan.myshiro1.mapper.UsersMapper">
<select id="findPermissionById" resultType="com.itbaizhan.myshiro1.domain.Permission">
SELECT DISTINCT permission.pid,permission.permissionName,permission.url FROM
users
LEFT JOIN users_role on users.uid = users_role.uid
LEFT JOIN role on users_role.rid = role.rid
LEFT JOIN role_permission on role.rid = role_permission.rid
LEFT JOIN permission on role_permission.pid = permission.pid
where users.uid = #{uid}
</select>
</mapper>
在Realm进行授权
// 自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.拿到用户认证信息
Users users = (Users) principalCollection.getPrimaryPrincipal();
//2.从数据库中查询权限
List<Permission> permissions = usersMapper.findPermissionById(users.getUid());
//3.遍历权限对象,将所有权限名交给Shiro管理
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (Permission permission : permissions) {
authorizationInfo.addStringPermission(permission.getUrl());
}
return authorizationInfo;
}
过滤器配置鉴权
Shiro可以根据用户拥有的权限,控制具体资源的访问,这一过程称为鉴权。在Shiro中,可以通过过滤器进行鉴权配置:
过滤器 | 说明 |
---|---|
roles | 角色授权过滤器,验证用户是否拥有某角色 |
perms | 权限授权过滤器,验证用户是否拥有某权限 |
port | 端口过滤器,表示可以通过的端口,如果配置端口为80,而用户访问该页面是非80,自动将请求端口改为80并重定向到该80端口 |
rest | rest风格过滤器,自动根据请求方法构建权限字符串 |
ssl | SSL过滤器,只有请求协议是https才能通过,否则自动跳转会https端口(443) |
1.配置过滤器
// 配置过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
// 1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
// 2.过滤器工厂设置SecurityManager
filterFactory.setSecurityManager(securityManager);
// 3.设置shiro的拦截规则
Map<String,String> filterMap=new HashMap<>();
// 不拦截的资源
filterMap.put("/login.html","anon");
filterMap.put("/fail.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/css/**","anon");
// 鉴权过滤器,要写在/**之前,否则认证都无法通过
filterMap.put("/reportform/find", "perms[/reportform/find]");
filterMap.put("/salary/find", "perms[/salary/find]");
filterMap.put("/staff/find", "perms[/staff/find]");
// 其余资源都需要认证,authc过滤器表示需要认证才能进行访问; user过滤器表示配置记住我或认证都可以访问
// filterMap.put("/**","authc");
filterMap.put("/user/pay","authc");
filterMap.put("/**", "user");
// 4.将拦截规则设置给过滤器工厂
filterFactory.setFilterChainDefinitionMap(filterMap);
// 5.登录页面
filterFactory.setLoginUrl("/login.html");
return filterFactory;
}
// 编写测试控制器
@RestController
public class MyController {
@GetMapping("/reportform/find")
public String findReportform(){
return "查询报表";
}
@GetMapping("/salary/find")
public String findSalary(){
return "查询工资";
}
@GetMapping("/staff/find")
public String findStaff(){
return "查询员工";
}
}
2.编写权限不足页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>权限不足</title>
</head>
<body>
<h1>您的权限不足,请联系管理员!</h1>
</body>
</html>
3.配置权限不足的跳转页面
// 配置过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
// 1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory=new ShiroFilterFactoryBean();
// 2.过滤器工厂设置SecurityManager
filterFactory.setSecurityManager(securityManager);
// 3.设置shiro的拦截规则
Map<String,String> filterMap=new HashMap<>();
// 不拦截的资源
filterMap.put("/login.html","anon");
filterMap.put("/fail.html","anon");
filterMap.put("/noPermission.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/css/**","anon");
// 鉴权过滤器
filterMap.put("/reportform/find", "perms[/reportform/find]");
filterMap.put("/salary/find", "perms[/salary/find]");
filterMap.put("/staff/find", "perms[/staff/find]");
// 其余资源都需要认证,authc过滤器表示需要认证才能进行访问; user过滤器表示配置记住我或认证都可以访问
// filterMap.put("/**","authc");
filterMap.put("/user/pay","authc");
filterMap.put("/**", "user");
// 4.将拦截规则设置给过滤器工厂
filterFactory.setFilterChainDefinitionMap(filterMap);
// 5.登录页面
filterFactory.setLoginUrl("/login.html");
// 6.权限不足访问的页面
filterFactory.setUnauthorizedUrl("/noPermission.html");
return filterFactory;
}
注解配置鉴权
Shiro支持注解配置鉴权
注解 | 说明 |
---|---|
@RequiresGuest | 不认证即可访问的资源 |
@RequiresUser | 通过登录方式或“记住我”方式认证后可以访问资源 |
@RequiresAuthentication | 通过登录方式认证后可以访问资源 |
@RequiresRoles | 认证用户拥有特定角色才能访问的资源 |
@RequiresPermissions | 认证用户拥有特定权限才能访问的资源 |
1.在配置类开启Shiro注解
// 开启shiro注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
// 开启aop注解支持
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
2.在控制器方法上添加鉴权注解
@GetMapping("/test/index")
@RequiresGuest
public String testIndex() {
return "访问首页";
}
@GetMapping("/test/user")
@RequiresUser
public String testUser() {
return "用户中心";
}
@GetMapping("/test/pay")
@RequiresAuthentication
public String testPay() {
return "支付中心";
}
@GetMapping("/tax/find")
@RequiresPermissions("/tax/find")
public String taxFind() {
return "查询税务";
}
@GetMapping("/address/find")
@RequiresPermissions("/address/find")
public String addressFind() {
return "查询地址";
}
@RequiresRoles("admin")
@RequiresPermissions("/admin")
public String adminMethod() {
return "admin页面"
}
缓存权限
每次鉴权都需要去数据库查询,这样非常浪费数据库资源,因为用户的权限短时间内是不会改变的,所以我们可以把权限保存在缓存中,这样我们就可以不用每次都去查询数据库了,可以提高系统性能。
Shiro整合ehcache
ehcache是用来管理缓存的一个工具,其缓存的数据可以放在内存中,也可以放在硬盘上。ehcache的核心是CacheManager,一切的ehcache的应用都是从CacheManager开始的。
1.引入shiro和ehcache整合包
<!-- shiro和ehcache整合包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.9.0</version>
</dependency>
2.创建配置文件shiro-ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--
默认缓存设置
maxElementsInMemory:缓存最大数目
maxEntriesLocalHeap:指定允许在内存中存放元素的最大数量。
timeToIdleSeconds:一个元素在不被请求的情况下允许在缓存中存活的最大时间。0表示永久有效。
timeToLiveSeconds:无论一个元素闲置与否,其允许在Cache中存活的最大时间。0表示永久有效。
diskExpiryThreadIntervalSeconds:检查元素是否过期的线程多久运行一次
-->
<defaultCache
maxElementsInMemory="10000"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"/>
<!-- 授权缓存设置 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
timeToIdleSeconds="0"
timeToLiveSeconds="0">
</cache>
</ehcache>
3.在配置文件创建CacheManager
// 创建CacheManager
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
return ehCacheManager;
}
4.在SecurityManager中配置CacheManager
@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm,
MyRealm2 myRealm2,
SessionManager sessionManager,
CookieRememberMeManager rememberMeManager,
EhCacheManager ehCacheManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义Realm放入SecurityManager中
// securityManager.setRealm(myRealm);
// 设置Realm管理者(需要设置在Realm之前)
securityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList();
realms.add(myRealm);
// realms.add(myRealm2);
securityManager.setRealms(realms);
securityManager.setSessionManager(sessionManager);
securityManager.setRememberMeManager(rememberMeManager);
securityManager.setCacheManager(ehCacheManager);
return securityManager;
}
总结
提示:这里对文章进行总结:
总的来说,Shiro 为我们提供了一个强大而灵活的授权解决方案。通过深入理解 Shiro 的授权概念和流程,我们可以更好地利用其特性来保护我们的应用程序。