目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Shiro

什么是权限控制

 将指定用户绑定到指定资源上,只能对指定资源进行(CRUD)操作。

限框架核心知识 ACL 和 RBAC

ACL: Access Control List 访问控制列表
 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩。
 例子:常见的文件系统权限设计(Linux 文件系统权限设计), 直接给用户加权限

  • 优点:简单易用,开发便捷。
  • 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理。

RBAC: Role Based Access Control
 基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。
 例子:基于 RBAC 模型的权限验证框架与应用 Apache Shiro、Spring Security。

  • 优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
  • 缺点:开发对比 ACL 相对复杂

 总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC 等。

Apache Shiro VS Spring Security

Spring Security
 Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC,DI(控制反转 Inversion of Control ,DI:Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
 一句话:Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架

Apache Shiro
 Apache Shiro 是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码和会话管理。使用 Shiro 的易于理解的 API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
 一句话:Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能

优缺点对比

  • Apache Shiro 比 Spring Security , 前者使用更简单
  • Shiro 功能强大、 简单、灵活, 不跟任何的框架或者容器绑定,可以独立运行
  • Spring Security 对 Spring 体系支持比较好,脱离 Spring 体系则很难开发
  • SpringSecutiry 支持 OAuth 鉴权 https://spring.io/projects/spring-security-oauth,Shiro 需要自己实现。

 总结:两个框架没有谁超过谁,大体功能一致,新手一般先推荐 Shiro,学习会容易点。

Shiro 核心知识之架构图交互和四大模块讲解

ShiroFeatures

  • 身份认证(Authentication):一般指登录。
  • 授权(Authorization):用户分配角色或者访问某些资源的权限。
  • 会话管理(Session Management): 用户的会话管理员,多数情况下是 Web session。
  • 加密(Cryptography):数据加解密,比如密码加解密等。

用户访问 Shrio 权限控制运行流程和常见概念

ShiroArchitecture

  • Subject:我们把用户或者程序称为主题(如用户,第三方服务,cron 作业),主题去访问系统或者资源。
  • SecurityManager:安全管理器,Subject 的认证和授权都要在安全管理器下进行
  • Authenticator:认证器,主要负责 Subject 的认证。
  • Realm:数据域,Shiro 和安全数据的连接器,好比 JDBC 连接数据库; 通过 realm 获取认证授权相关信息。
  • Authorizer:授权器,主要负责 Subject 的授权, 控制 subject 拥有的角色或者权限。
  • Cryptography:加解密,Shiro 的包含易于使用和理解的数据加解密方法,简化了很多复杂的 API。
  • Cache Manager:缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能。

Reference

Apache Shiro 入门示例

https://start.spring.io/ 添加项目依赖

 1<dependency>
 2			<groupId>org.springframework.boot</groupId>
 3			<artifactId>spring-boot-starter-web</artifactId>
 4		</dependency>
 5
 6		<dependency>
 7			<groupId>mysql</groupId>
 8			<artifactId>mysql-connector-java</artifactId>
 9			<!--注释掉-->
10			<!--<scope>runtime</scope>-->
11		</dependency>
12
13		<dependency>
14			<groupId>org.springframework.boot</groupId>
15			<artifactId>spring-boot-starter-test</artifactId>
16			<scope>test</scope>
17		</dependency>
18
19		<!--阿里巴巴druid数据源-->
20		<dependency>
21			<groupId>com.alibaba</groupId>
22			<artifactId>druid</artifactId>
23			<version>1.1.6</version>
24		</dependency>
25
26		<!--spring整合shiro-->
27		<dependency>
28			<groupId>org.apache.shiro</groupId>
29			<artifactId>shiro-spring</artifactId>
30			<version>1.4.0</version>
31		</dependency>

快速上手认证和授权流程
认证流程

示例

 1import org.apache.shiro.SecurityUtils;
 2import org.apache.shiro.authc.UsernamePasswordToken;
 3import org.apache.shiro.mgt.DefaultSecurityManager;
 4import org.apache.shiro.realm.SimpleAccountRealm;
 5import org.apache.shiro.subject.Subject;
 6import org.junit.Before;
 7import org.junit.Test;
 8
 9/**
10 * 单元测试用例执行顺序
11 *
12 * @BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
13 */
14public class QuickStartTest4_2 {
15
16    //创建Realm
17    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();
18    //创建SecurityManager
19    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
20
21    @Before
22    //初始化Realm、SecurityManager
23    public void init() {
24        //初始化Realm (数据源)
25        accountRealm.addAccount("xdclass", "123");
26        accountRealm.addAccount("jack", "456");
27
28        //构建SecurityManager并且绑定Realm
29        defaultSecurityManager.setRealm(accountRealm);
30    }
31
32    @Test
33    //测试
34    //SecurityUtils用于绑定SecurityManager,以及获取Subject。
35    public void testAuthentication() {
36        //将SecurityManager设置成上下文
37        SecurityUtils.setSecurityManager(defaultSecurityManager);
38
39        //以及获取Subject:获取当前操作主体, application user
40        Subject subject = SecurityUtils.getSubject();
41
42        //用户输入的账号密码
43        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "456");
44        subject.login(usernamePasswordToken);
45
46        //进行认证
47        System.out.println("认证结果:"+subject.isAuthenticated());
48    }
49}

Shiro 认证和授权流程和常用 API 梳理
授权流程

 1//是否有对应的角色
 2subject.hasRole("root")
 3
 4//获取subject名
 5subject.getPrincipal()
 6
 7//检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
 8subject.checkRole("admin")
 9
10//检查是否有对应的角色
11subject.hasRole("admin")
12
13//退出登录
14subject.logout();

示例代码

 1import org.apache.shiro.SecurityUtils;
 2import org.apache.shiro.authc.UsernamePasswordToken;
 3import org.apache.shiro.mgt.DefaultSecurityManager;
 4import org.apache.shiro.realm.SimpleAccountRealm;
 5import org.apache.shiro.subject.Subject;
 6import org.junit.Before;
 7import org.junit.Test;
 8
 9/**
10 * 单元测试用例执行顺序
11 *
12 * @BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
13 */
14public class QuickStartTest4_3 {
15    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();
16    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
17
18    @Before
19    public void init() {
20        //初始化Realm (数据源)    每个账户添加相应的角色
21        accountRealm.addAccount("xdclass", "123","root","admin");
22        accountRealm.addAccount("jack", "456","user");
23
24        //SecurityManager绑定Realm
25        defaultSecurityManager.setRealm(accountRealm);
26    }
27
28    @Test
29    public void testAuthentication() {
30        //将SecurityManager加入上下文
31        SecurityUtils.setSecurityManager(defaultSecurityManager);
32
33        //获取Subject 主体
34        Subject subject = SecurityUtils.getSubject();
35
36        //用户输入的账号密码
37        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "456");
38        subject.login(usernamePasswordToken);
39
40        //是否认证通过:true
41        System.out.println(" 认证结果:"+subject.isAuthenticated());
42        
43        //是否有对应的角色,有返回值(boolean)    false   (jack拥有的角色是user)
44        System.out.println(" 是否有对应的root角色:"+subject.hasRole("root"));
45        
46        //获取subject主体的用户名:  jack
47        System.out.println(" getPrincipal=" + subject.getPrincipal());
48        
49        //检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
50        subject.checkRole("user");
51        
52        //退出登录
53        subject.logout();   //Stopping session with id [70b2429e-0e35-4822-9e5d-46ebeda35b36]
54
55        //退出登录后的的认证结果:  false
56        System.out.println("logout后认证结果:"+subject.isAuthenticated());
57    }
58}

Apache Shiro Realm

 realm 作用:Shiro 从 Realm 获取安全数据;默认自带的 realm:IDEA 查看 realm 继承关系,有默认实现和自定义继承的 realm。
两个概念

  • principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
  • credential:凭证, 一般就是密码。(所以一般我们说 principal + credential 就账号 + 密码),开发中,往往是自定义 realm , 即继承 AuthorizingRealm。

Shiro 内置 IniRealm 实操和权限验证 API

测试类

 1import org.apache.shiro.SecurityUtils;
 2import org.apache.shiro.authc.UsernamePasswordToken;
 3import org.apache.shiro.config.IniSecurityManagerFactory;
 4import org.apache.shiro.mgt.SecurityManager;
 5import org.apache.shiro.subject.Subject;
 6import org.apache.shiro.util.Factory;
 7import org.junit.Test;
 8
 9/**
10 * 单元测试用例执行顺序
11 *
12 */
13public class QuickStartTest5_2 {
14    @Test
15    public void testAuthentication() {
16
17        //创建SecurityManager工厂,通过配置文件shiro.ini创建
18        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
19
20        SecurityManager securityManager = factory.getInstance();
21
22        //将securityManager 设置到当前运行环境中
23        SecurityUtils.setSecurityManager(securityManager);
24
25        Subject subject = SecurityUtils.getSubject();
26
27        //用户输入的账号密码
28        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "456");
29
30        subject.login(usernamePasswordToken);
31        
32        System.out.println(" 认证结果:"+subject.isAuthenticated());
33
34        System.out.println(" 是否有对应的user角色:"+subject.hasRole("user"));
35
36        System.out.println(" getPrincipal=" + subject.getPrincipal());
37
38        subject.checkRole("user");
39
40        subject.checkPermission("video:find");
41
42        System.out.println( "是否有video:find 权限:"+ subject.isPermitted("video:find"));
43
44        subject.logout();
45
46        System.out.println("logout后认证结果:"+subject.isAuthenticated());
47    }
48    
49    @Test
50    public void testAuthentication2() {
51
52        //创建SecurityManager工厂,通过配置文件ini创建
53        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
54
55        SecurityManager securityManager = factory.getInstance();
56
57        //将securityManager 设置到当前运行环境中
58        SecurityUtils.setSecurityManager(securityManager);
59
60        Subject subject = SecurityUtils.getSubject();
61
62        //用户输入的账号密码
63        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("xdclass", "123");
64
65        subject.login(usernamePasswordToken);
66        
67        System.out.println(" 认证结果:"+subject.isAuthenticated());
68
69        System.out.println(" 是否有对应的admin角色:"+subject.hasRole("admin"));
70
71        System.out.println(" getPrincipal=" + subject.getPrincipal());
72
73        subject.checkPermission("video:find");
74
75        System.out.println( "是否有video:find 权限:"+ subject.isPermitted("video:find"));
76        
77        subject.logout();
78
79        System.out.println("logout后认证结果:"+subject.isAuthenticated());
80    }
81}

在资源目录下创建 shiro.ini 文件

 1# 格式 name=password,role1,role2,..roleN
 2[users]
 3jack = 456, user
 4xdclass = 123, root, admin
 5
 6# 格式 role=permission1,permission2...permissionN   也可以用通配符
 7# 下面配置user的权限为所有video:find,video:buy,如果需要配置video全部操作crud 则 user = video:*
 8[roles]
 9user = video:find,video:buy
10visitor = good:find,good:buy,comment:*
11
12# 'admin' role has all permissions, indicated by the wildcard '*'
13admin = *

Shiro 内置 JdbcRealm

SQL 脚本

 1/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 2/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 3/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 4/*!40101 SET NAMES utf8 */;
 5/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
 6/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
 7/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
 8# Dump of table roles_permissions
 9# ------------------------------------------------------------
10DROP TABLE IF EXISTS `roles_permissions`;
11
12CREATE TABLE `roles_permissions` (
13  `id` bigint(20) NOT NULL AUTO_INCREMENT,
14  `role_name` varchar(100) DEFAULT NULL,
15  `permission` varchar(100) DEFAULT NULL,
16  PRIMARY KEY (`id`),
17  UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`)
18) ENGINE=InnoDB DEFAULT CHARSET=utf8;
19
20LOCK TABLES `roles_permissions` WRITE;
21/*!40000 ALTER TABLE `roles_permissions` DISABLE KEYS */;
22
23INSERT INTO `roles_permissions` (`id`, `role_name`, `permission`)
24VALUES
25	(4,'admin','video:*'),
26	(3,'role1','video:buy'),
27	(2,'role1','video:find'),
28	(5,'role2','*'),
29	(1,'root','*');
30
31/*!40000 ALTER TABLE `roles_permissions` ENABLE KEYS */;
32UNLOCK TABLES;
33# Dump of table user_roles
34# ------------------------------------------------------------
35
36DROP TABLE IF EXISTS `user_roles`;
37
38CREATE TABLE `user_roles` (
39  `id` bigint(20) NOT NULL AUTO_INCREMENT,
40  `username` varchar(100) DEFAULT NULL,
41  `role_name` varchar(100) DEFAULT NULL,
42  PRIMARY KEY (`id`),
43  UNIQUE KEY `idx_user_roles` (`username`,`role_name`)
44) ENGINE=InnoDB DEFAULT CHARSET=utf8;
45
46LOCK TABLES `user_roles` WRITE;
47/*!40000 ALTER TABLE `user_roles` DISABLE KEYS */;
48
49INSERT INTO `user_roles` (`id`, `username`, `role_name`)
50VALUES
51	(1,'jack','role1'),
52	(2,'jack','role2'),
53	(4,'xdclass','admin'),
54	(3,'xdclass','root');
55
56/*!40000 ALTER TABLE `user_roles` ENABLE KEYS */;
57UNLOCK TABLES;
58
59# Dump of table users
60# ------------------------------------------------------------
61
62DROP TABLE IF EXISTS `users`;
63
64CREATE TABLE `users` (
65  `id` bigint(20) NOT NULL AUTO_INCREMENT,
66  `username` varchar(100) DEFAULT NULL,
67  `password` varchar(100) DEFAULT NULL,
68  `password_salt` varchar(100) DEFAULT NULL,
69  PRIMARY KEY (`id`),
70  UNIQUE KEY `idx_users_username` (`username`)
71) ENGINE=InnoDB DEFAULT CHARSET=utf8;
72
73LOCK TABLES `users` WRITE;
74/*!40000 ALTER TABLE `users` DISABLE KEYS */;
75
76INSERT INTO `users` (`id`, `username`, `password`, `password_salt`)
77VALUES
78	(1,'jack','123',NULL),
79	(2,'xdclass','456',NULL);
80
81/*!40000 ALTER TABLE `users` ENABLE KEYS */;
82UNLOCK TABLES;
83/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
84/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
85/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
86/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
87/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
88/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

在资源目录下创建 jdbcrealm.ini 文件

 1#注意 文件格式必须为ini,编码为ANSI
 2
 3#声明Realm,指定realm类型
 4jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
 5
 6#配置数据源
 7#dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
 8dataSource=com.alibaba.druid.pool.DruidDataSource
 9
10# mysql-connector-java 5 用的驱动url是com.mysql.jdbc.Driver,mysql-connector-java6以后用的是com.mysql.cj.jdbc.Driver
11dataSource.driverClassName=com.mysql.cj.jdbc.Driver
12
13#避免安全警告
14dataSource.url=jdbc:mysql://192.168.31.251:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
15dataSource.username=root
16dataSource.password=soulboy
17
18#指定数据源
19jdbcRealm.dataSource=$dataSource
20
21#开启查找权限
22jdbcRealm.permissionsLookupEnabled=true
23
24#指定SecurityManager的Realms实现,设置realms,可以有多个,用逗号隔开
25securityManager.realms=$jdbcRealm

测试类

 1/**
 2 * 单元测试用例执行顺序
 3 *
 4 */
 5public class QuickStartTest5_3 {
 6
 7    @Test
 8    public void testAuthentication() {
 9
10        //创建SecurityManager工厂,通过配置文件ini创建
11        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbcrealm.ini");
12
13        SecurityManager securityManager = factory.getInstance();
14
15        //将securityManager 设置到当前运行环境中
16        SecurityUtils.setSecurityManager(securityManager);
17
18        Subject subject = SecurityUtils.getSubject();
19
20        //用户输入的账号密码
21        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
22
23        subject.login(usernamePasswordToken);
24
25        //org.apache.shiro.realm.jdbc.JdbcRealm
26
27        System.out.println(" 认证结果:"+subject.isAuthenticated());
28
29        System.out.println(" 是否有对应的role1角色:"+subject.hasRole("role1"));
30
31        System.out.println("是否有video:find权限:"+ subject.isPermitted("video:find"));
32    }
33    
34    @Test
35	//方式二
36    public void test2(){
37        DefaultSecurityManager securityManager = new DefaultSecurityManager();
38
39        DruidDataSource ds = new DruidDataSource();
40        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
41        ds.setUrl("jdbc:mysql://192.168.31.251:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
42        ds.setUsername("test");
43        ds.setPassword("Xdclasstest");
44        
45	//创建JdbcRealm,
46        JdbcRealm jdbcRealm = new JdbcRealm();
47        jdbcRealm.setPermissionsLookupEnabled(true);
48        jdbcRealm.setDataSource(ds);
49
50	
51	//将DruidDataSource绑定到Realm
52        securityManager.setRealm(jdbcRealm);
53
54        //将securityManager 设置到当前运行环境中
55        SecurityUtils.setSecurityManager(securityManager);
56
57        Subject subject = SecurityUtils.getSubject();
58
59        //用户输入的账号密码
60        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
61
62        subject.login(usernamePasswordToken);
63        
64        System.out.println(" 认证结果:"+subject.isAuthenticated());
65
66        System.out.println(" 是否有对应的role1角色:"+subject.hasRole("role1"));
67
68        System.out.println("是否有video:find权限:"+ subject.isPermitted("video:find"));
69
70        System.out.println("是否有任意权限:"+ subject.isPermitted("aaaa:xxxxxxxxx"));
71    }
72}

自定义 Realm

步骤

  • 创建一个类 ,继承 AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm
  • 重写授权方法 doGetAuthorizationInfo
  • 重写认证方法 doGetAuthenticationInfo

方法

  • 当用户登陆的时候会调用 doGetAuthenticationInfo
  • 进行权限校验的时候会调用: doGetAuthorizationInfo

对象介绍

1* UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential
2    * UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
3* SimpleAuthorizationInfo:代表用户角色权限信息
4* SimpleAuthenticationInfo :代表该用户的认证信息

定义 Realm 类

  1import org.apache.shiro.authc.AuthenticationException;
  2import org.apache.shiro.authc.AuthenticationInfo;
  3import org.apache.shiro.authc.AuthenticationToken;
  4import org.apache.shiro.authc.SimpleAuthenticationInfo;
  5import org.apache.shiro.authz.AuthorizationInfo;
  6import org.apache.shiro.authz.SimpleAuthorizationInfo;
  7import org.apache.shiro.realm.AuthorizingRealm;
  8import org.apache.shiro.subject.PrincipalCollection;
  9import java.util.*;
 10
 11/**
 12 * 自定义realm
 13 */
 14public class CustomRealm extends AuthorizingRealm {
 15
 16    private final Map<String,String> userInfoMap = new HashMap<>();
 17    //初始化:userInfoMap
 18    {
 19        userInfoMap.put("jack","123");
 20        userInfoMap.put("xdclass","456");
 21    }
 22
 23    //role -> permission
 24    private final Map<String,Set<String>> permissionMap = new HashMap<>();
 25    //初始化:permissionMap
 26    {
 27        Set<String> set1 = new HashSet<>();
 28        Set<String> set2 = new HashSet<>();
 29
 30        set1.add("video:find");
 31        set1.add("video:buy");
 32
 33        set2.add("video:add");
 34        set2.add("video:delete");
 35
 36        permissionMap.put("user",set1);
 37        permissionMap.put("admin",set2);
 38    }
 39
 40    //user -> role
 41    private final Map<String,Set<String>> roleMap = new HashMap<>();
 42    //初始化roleMap
 43    {
 44        Set<String> set1 = new HashSet<>();
 45        Set<String> set2 = new HashSet<>();
 46
 47        set1.add("user");
 48
 49        set2.add("admin");
 50
 51        roleMap.put("jack",set1);
 52        roleMap.put("xdclass",set2);
 53    }
 54
 55    //doGetAuthorizationInfo(获取授权信息): 进行权限校验的时候会调用
 56    @Override
 57    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
 58        System.out.println("权限 doGetAuthorizationInfo");
 59
 60        //拿到用户名
 61        String name = (String)principals.getPrimaryPrincipal();
 62
 63        //根据user查role
 64        Set<String> roles = getRolesByNameFromDB(name);
 65
 66        //根据role查permission:该用户拥有所有角色的权限集合
 67        Iterator<String> it = roles.iterator();
 68        Set<String> permissions = null;
 69        while(it.hasNext()){
 70            String role = it.next();
 71            Set<String> permission = getPermissionsByRoleFromDB(role);
 72            if(permissions == null){
 73                permissions = permission;
 74            }
 75            //并集
 76            permissions.addAll(permission);
 77        }
 78
 79        //Set<String> permissions = getPermissionsByRoleFromDB(role);
 80
 81        //封装simpleAuthorizationInfo类
 82        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
 83        //封装roles
 84        simpleAuthorizationInfo.setRoles(roles);
 85        //封装permissions
 86        simpleAuthorizationInfo.setStringPermissions(permissions);
 87        return simpleAuthorizationInfo;
 88    }
 89
 90    //doGetAuthenticationInfo(获取身份验证信息): 当用户登陆的时候会调用
 91    @Override
 92    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
 93
 94        System.out.println("认证 doGetAuthenticationInfo");
 95
 96        //从token获取身份信息,token代表用户输入的信息,获取用户名。
 97        String name = (String)token.getPrincipal();
 98
 99        //模拟从数据库中取密码
100        String pwd = getPwdByUserNameFromDB(name);
101
102        //如果没有找到密码返回null
103        if( pwd == null || "".equals(pwd)){
104            return null;
105        }
106
107        //找到密码则把用户名和密码封装到SimpleAuthenticationInfo中返回:SecurityManager使用SimpleAuthenticationInfo自动做校验
108        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());
109        return simpleAuthenticationInfo;
110    }
111
112    /**
113     * 模拟从数据库获取用户角色集合
114     * @param name
115     * @return
116     */
117    private Set<String> getRolesByNameFromDB(String name) {
118        return roleMap.get(name);
119    }
120
121    /**
122     *  模拟从数据库获取权限集合
123     * @param name
124     * @return
125     */
126    private Set<String> getPermissionsByRoleFromDB(String name) {
127        return permissionMap.get(name);
128    }
129
130    private String getPwdByUserNameFromDB(String name) {
131        return userInfoMap.get(name);
132    }
133}

测试类

 1import org.apache.shiro.SecurityUtils;
 2import org.apache.shiro.authc.UsernamePasswordToken;
 3import org.apache.shiro.config.IniSecurityManagerFactory;
 4import org.apache.shiro.mgt.DefaultSecurityManager;
 5import org.apache.shiro.mgt.SecurityManager;
 6import org.apache.shiro.subject.Subject;
 7import org.apache.shiro.util.Factory;
 8import org.junit.Before;
 9import org.junit.Test;
10
11/**
12 * 单元测试用例执行顺序
13 *
14 */
15public class QuickStartTest5_4 {
16
17    //创建自定义Realm
18    private CustomRealm customRealm = new CustomRealm();
19    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
20
21    @Before
22    public void init(){
23        //SecurityManager绑定自定义Realm
24        defaultSecurityManager.setRealm(customRealm);
25        //SecurityManager绑定到上下文中
26        SecurityUtils.setSecurityManager(defaultSecurityManager);
27    }
28
29    @Test
30    public void testAuthentication() {
31
32        //获取当前操作的主体
33        Subject subject = SecurityUtils.getSubject();
34
35        //用户输入的账号密码
36        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
37        subject.login(usernamePasswordToken);
38
39        //登录
40        System.out.println(" 认证结果:"+subject.isAuthenticated());
41
42        //拿到主体标示属性
43        System.out.println(" getPrincipal=" + subject.getPrincipal());
44
45        subject.checkRole("user");
46
47        System.out.println("是否有对应的角色:"+subject.hasRole("user"));
48
49        System.out.println("是否有对应的权限:"+subject.isPermitted("video:find"));
50    }
51}

Shiro 常用的内置 Filter

 核心过滤器类是 DefaultFilter。
 配置哪个路径对应哪个拦截器进行处理,URL 需要有一定的命名规范:/pub(游客)、/user(用户)、/admin(管理员)等对应不同的 Filter 处理。

  • authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
1需要认证登录才能访问
  • user:org.apache.shiro.web.filter.authc.UserFilter
1用户拦截器,表示必须存在用户。
  • anon:org.apache.shiro.web.filter.authc.AnonymousFilter
1匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
  • roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
1角色授权拦截器,验证用户是或否拥有角色。  
2参数可写多个,表示某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过
  • perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
1权限授权拦截器,验证用户是否拥有权限  
2参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算可以
  • authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
1httpBasic 身份验证拦截器。
  • logout:org.apache.shiro.web.filter.authc.LogoutFilter
1退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl();	设置的 url
  • port:org.apache.shiro.web.filter.authz.PortFilter
1端口拦截器, 可通过的端口。
  • ssl:org.apache.shiro.web.filter.authz.SslFilter
1ssl拦截器,只有请求协议是https才能通过

Filter 路径配置

 路径通配符支持: ?、*、**。注意通配符匹配不 包括目录分隔符“/”
 * 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个 * 来匹配。
匹配规则说明

1URL权限采取第一次匹配优先的方式
2? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
3* : 匹配零个或多个字符串,如 /user* ,匹配 /usertest,但不匹配 /user/1
4** : 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy

规则优先级
 优先命中原则,先配置的为主。
 性能问题:通配符比字符串匹配会复杂点,所以性能也会稍弱,推荐是使用字符串匹配方式。

1/user/**=filter1  
2/user/add=filter2  
3  
4请求 /user/add  命中的是filter1拦截器

Shiro 数据安全之数据加解密

  • 散列算法:一般叫 hash,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数,适合存储密码,比如 MD5。
  • salt(盐):如果直接通过散列函数得到加密数据,容易被对应解密网站暴力破解,一般会在应用程序里面加特殊的自动进行处理,比如用户 id,例子:加密数据 = MD5(明文密码 + 用户 id), 破解难度会更大,也可以使用多重散列,比如多次 md5。
  • Shiro 里面 CredentialsMatcher,用来验证密码是否正确。
 1//源码:AuthenticatingRealm -> assertCredentialsMatch()
 2//一般会自定义验证规则
 3	@Bean
 4    public HashedCredentialsMatcher hashedCredentialsMatcher(){
 5        HashedCredentialsMatcher hashedCredentialsMatcher = new 		HashedCredentialsMatcher();
 6        
 7        //散列算法,使用MD5算法;
 8        hashedCredentialsMatcher.setHashAlgorithmName("md5");
 9
10        //散列的次数,比如散列两次,相当于 md5(md5("xxx"));
11        hashedCredentialsMatcher.setHashIterations(2);
12        
13        return hashedCredentialsMatcher;
14    }

Shiro 权限控制注解方式、编程方式

配置文件的方式
 使用 ShiroConfig

注解方式

  • @RequiresRoles(value={"admin", "editor"}, logical= Logical.AND):需要角色 admin 和 editor 两个角色 AND 表示两个同时成立。
  • @RequiresPermissions (value={"user:add", "user:del"}, logical= Logical.OR):需要权限 user:add 或 user:del 权限其中一个,OR 是或的意思。
  • @RequiresAuthentication:已经授过权,调用 Subject.isAuthenticated()返回 true。
  • @RequiresUser:身份验证或者通过记 住我登录的。
 1import org.apache.shiro.authz.annotation.Logical;
 2import org.apache.shiro.authz.annotation.RequiresPermissions;
 3import org.apache.shiro.authz.annotation.RequiresRoles;
 4import org.springframework.stereotype.Controller;
 5import org.springframework.web.bind.annotation.RequestMapping;
 6import org.springframework.web.bind.annotation.RestController;
 7
 8@Controller
 9@RequestMapping("/api/admin/user")//admin代表权限,user代表模块
10public class UserController {
11
12    //需要同时拥有admin和editor两个角色才可以访问。logical= Logical.AND
13    //需要同时拥有admin或editor两个角色才可以访问。logical= Logical.OR
14    //@RequiresRoles(value={"admin", "editor"}, logical= Logical.OR)
15    //需要权限"user:add", "user:del"
16    @RequiresPermissions(value={"user:add", "user:del"}, logical= Logical.OR)
17    @RequestMapping("/list_user")
18    public Object listUser(){
19        return null;
20    }
21}

编程方式

  • subject.hasRole("xxx");
  • subject.isPermitted("xxx");
  • subject. isPermittedAll("xxxxx","yyyy");
  • subject.checkRole("xxx"); // 无返回值,可以认为内部使用断言的方式
 1Subject subject = SecurityUtils.getSubject(); 
 2//基于角色判断
 3if(subject.hasRole(admin)) {
 4	//有角色,有权限
 5} else {
 6	//无角色,无权限
 7	
 8}
 9//或者权限判断
10if(subject.isPermitted("/user/add")){
11    //有权限
12}else{
13    //无权限
14}

Shiro 缓存模块

 shiro 中提供了对认证信息和授权信息的缓存。 默认是关闭认证信息缓存的,对于授权信息的缓存 shiro 默认开启的(因为授权的数据量大).

  • AuthenticatingRealm 提供了对 AuthenticationInfo 信息的缓存。认证!
  • AuthorizingRealm 提供了对 AuthorizationInfo 信息的缓存。授权!

Shiro Session 模块

会话 session
 用户和程序直接的链接,程序可以根据 session 识别到哪个用户,和 javaweb 中的 session 类似。

会话管理器 SessionManager
 会话管理器管理所有 subject 的所有操作,是 shiro 的核心组件。v

SessionDao 会话存储/持久化
 SessionDAO AbstractSessionDAO CachingSessionDAOEnterpriseCacheSessionDAO MemorySessionDAO
核心方法

 1//创建
 2Serializable create(Session session);
 3//获取
 4Session readSession(Serializable sessionId) throws UnknownSessionException;
 5//更新
 6void update(Session session) 
 7//删除,会话过期时会调用
 8void delete(Session session);
 9//获取活跃的session
10Collection<Session> getActiveSessions();

会话存储有多个实现

SpringBoot2.x 整合 Shiro 综合案例

表设计

  1# ************************************************************
  2# Sequel Pro SQL dump
  3# Version 4541
  4#
  5# http://www.sequelpro.com/
  6# https://github.com/sequelpro/sequelpro
  7#
  8# Host: 120.76.62.13 (MySQL 5.7.17)
  9# Database: xdclass_shiro
 10# Generation Time: 2019-05-12 13:44:51 +0000
 11# ************************************************************
 12
 13
 14/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 15/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 16/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 17/*!40101 SET NAMES utf8 */;
 18/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
 19/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
 20/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
 21
 22
 23# Dump of table permission
 24# ------------------------------------------------------------
 25
 26DROP TABLE IF EXISTS `permission`;
 27
 28CREATE TABLE `permission` (
 29  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 30  `name` varchar(128) DEFAULT NULL COMMENT '名称',
 31  `url` varchar(128) DEFAULT NULL COMMENT '接口路径',
 32  PRIMARY KEY (`id`)
 33) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 34
 35LOCK TABLES `permission` WRITE;
 36/*!40000 ALTER TABLE `permission` DISABLE KEYS */;
 37
 38INSERT INTO `permission` (`id`, `name`, `url`)
 39VALUES
 40	(1,'video_update','/api/video/update'),
 41	(2,'video_delete','/api/video/delete'),
 42	(3,'video_add','/api/video/add'),
 43	(4,'order_list','/api/order/list'),
 44	(5,'user_list','/api/user/list');
 45
 46/*!40000 ALTER TABLE `permission` ENABLE KEYS */;
 47UNLOCK TABLES;
 48
 49
 50# Dump of table role
 51# ------------------------------------------------------------
 52
 53DROP TABLE IF EXISTS `role`;
 54
 55CREATE TABLE `role` (
 56  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 57  `name` varchar(128) DEFAULT NULL COMMENT '名称',
 58  `description` varchar(64) DEFAULT NULL COMMENT '描述',
 59  PRIMARY KEY (`id`)
 60) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 61
 62LOCK TABLES `role` WRITE;
 63/*!40000 ALTER TABLE `role` DISABLE KEYS */;
 64
 65INSERT INTO `role` (`id`, `name`, `description`)
 66VALUES
 67	(1,'admin','普通管理员'),
 68	(2,'root','超级管理员'),
 69	(3,'editor','审核人员');
 70
 71/*!40000 ALTER TABLE `role` ENABLE KEYS */;
 72UNLOCK TABLES;
 73
 74
 75# Dump of table role_permission
 76# ------------------------------------------------------------
 77
 78DROP TABLE IF EXISTS `role_permission`;
 79
 80CREATE TABLE `role_permission` (
 81  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 82  `role_id` int(11) DEFAULT NULL,
 83  `permission_id` int(11) DEFAULT NULL,
 84  PRIMARY KEY (`id`)
 85) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 86
 87LOCK TABLES `role_permission` WRITE;
 88/*!40000 ALTER TABLE `role_permission` DISABLE KEYS */;
 89
 90INSERT INTO `role_permission` (`id`, `role_id`, `permission_id`)
 91VALUES
 92	(1,3,1),
 93	(2,3,2),
 94	(3,3,3),
 95	(4,2,1),
 96	(5,2,2),
 97	(6,2,3),
 98	(7,2,4);
 99
100/*!40000 ALTER TABLE `role_permission` ENABLE KEYS */;
101UNLOCK TABLES;
102
103
104# Dump of table user
105# ------------------------------------------------------------
106
107DROP TABLE IF EXISTS `user`;
108
109CREATE TABLE `user` (
110  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
111  `username` varchar(128) DEFAULT NULL COMMENT '用户名',
112  `password` varchar(256) DEFAULT NULL COMMENT '密码',
113  `create_time` datetime DEFAULT NULL,
114  `salt` varchar(128) DEFAULT NULL,
115  PRIMARY KEY (`id`)
116) ENGINE=InnoDB DEFAULT CHARSET=utf8;
117
118LOCK TABLES `user` WRITE;
119/*!40000 ALTER TABLE `user` DISABLE KEYS */;
120
121INSERT INTO `user` (`id`, `username`, `password`, `create_time`, `salt`)
122VALUES
123	(1,'二当家小D','123456',NULL,NULL),
124	(2,'大当家','123456789',NULL,NULL),
125	(3,'jack','123',NULL,NULL);
126
127/*!40000 ALTER TABLE `user` ENABLE KEYS */;
128UNLOCK TABLES;
129
130
131# Dump of table user_role
132# ------------------------------------------------------------
133
134DROP TABLE IF EXISTS `user_role`;
135
136CREATE TABLE `user_role` (
137  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
138  `role_id` int(11) DEFAULT NULL,
139  `user_id` int(11) DEFAULT NULL,
140  `remarks` varchar(64) DEFAULT NULL,
141  PRIMARY KEY (`id`)
142) ENGINE=InnoDB DEFAULT CHARSET=utf8;
143
144LOCK TABLES `user_role` WRITE;
145/*!40000 ALTER TABLE `user_role` DISABLE KEYS */;
146
147INSERT INTO `user_role` (`id`, `role_id`, `user_id`, `remarks`)
148VALUES
149	(1,3,1,'二当家小D是editor'),
150	(2,1,3,'jack是admin'),
151	(3,2,3,'jack是root'),
152	(4,3,3,'jack是editor'),
153	(5,1,2,'大当家是admin');
154
155/*!40000 ALTER TABLE `user_role` ENABLE KEYS */;
156UNLOCK TABLES;
157
158
159
160/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
161/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
162/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
163/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
164/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
165/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

引入 pom.xml 依赖

 1<dependency>
 2			<groupId>org.springframework.boot</groupId>
 3			<artifactId>spring-boot-starter-web</artifactId>
 4		</dependency>
 5
 6		<dependency>
 7			<groupId>org.mybatis.spring.boot</groupId>
 8			<artifactId>mybatis-spring-boot-starter</artifactId>
 9			<version>2.0.1</version>
10		</dependency>
11
12		<dependency>
13			<groupId>mysql</groupId>
14			<artifactId>mysql-connector-java</artifactId>
15			<!--注释掉-->
16			<!--<scope>runtime</scope>-->
17		</dependency>
18		<dependency>
19			<groupId>org.springframework.boot</groupId>
20			<artifactId>spring-boot-starter-test</artifactId>
21			<scope>test</scope>
22		</dependency>
23
24
25		<!--阿里巴巴druid数据源-->
26		<dependency>
27			<groupId>com.alibaba</groupId>
28			<artifactId>druid</artifactId>
29			<version>1.1.6</version>
30		</dependency>
31
32		<!--spring整合shiro-->
33		<dependency>
34			<groupId>org.apache.shiro</groupId>
35			<artifactId>shiro-spring</artifactId>
36			<version>1.4.0</version>
37		</dependency>

添加 application.properties

 1#==============================数据库相关配置========================================
 2spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
 3spring.datasource.url=jdbc:mysql://192.168.31.251:3306/shiros?useUnicode=true&characterEncoding=utf-8&useSSL=false
 4spring.datasource.username =root
 5spring.datasource.password =soulboy
 6#使用阿里巴巴druid数据源,默认使用自带的
 7#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
 8#开启控制台打印sql
 9mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
10
11# mybatis 下划线转驼峰配置,两者都可以
12#mybatis.configuration.mapUnderscoreToCamelCase=true
13mybatis.configuration.map-underscore-to-camel-case=true

开发用户-角色-权限 多对多关联查询 SQL
第一步 查询用户对应的角色映射关系

1select * from user u 
2left join user_role ur on u.id=ur.user_id
3where  u.id=3

第二步 查询用户对应的角色信息

1select * from user u 
2left join user_role ur on u.id=ur.user_id
3left join role r on ur.role_id = r.id
4where  u.id=3

第三步 查询角色和权限的关系

1select * from user u 
2left join user_role ur on u.id=ur.user_id
3left join role r on ur.role_id = r.id
4left join role_permission rp on r.id=rp.role_id
5where  u.id=1

第四步 查询角色对应的权限信息(某个用户具备的角色和权限集合)

1select * from user u 
2left join user_role ur on u.id=ur.user_id
3left join role r on ur.role_id = r.id
4left join role_permission rp on r.id=rp.role_id
5left join permission p on rp.permission_id=p.id
6where  u.id=1

启动类添加 Mapper 路径扫描注解

1@SpringBootApplication
2@MapperScan("net.xdclass.rbac_shiro.dao")
3public class RbacShiroApplication {
4
5	public static void main(String[] args) {
6		SpringApplication.run(RbacShiroApplication.class, args);
7	}
8}

Domain
Permission

 1public class Permission {
 2
 3    private int id;
 4
 5    private String name;
 6
 7    private String url;
 8
 9    public int getId() {
10        return id;
11    }
12
13    public void setId(int id) {
14        this.id = id;
15    }
16
17    public String getName() {
18        return name;
19    }
20
21    public void setName(String name) {
22        this.name = name;
23    }
24
25    public String getUrl() {
26        return url;
27    }
28
29    public void setUrl(String url) {
30        this.url = url;
31    }
32}

Role

 1/**
 2 * 角色
 3 */
 4public class Role {
 5
 6    private int id;
 7
 8    private String name;
 9
10    private String description;
11
12    private List<Permission> permissionList;
13
14    public List<Permission> getPermissionList() {
15        return permissionList;
16    }
17
18    public void setPermissionList(List<Permission> permissionList) {
19        this.permissionList = permissionList;
20    }
21
22    public int getId() {
23        return id;
24    }
25
26    public void setId(int id) {
27        this.id = id;
28    }
29
30    public String getName() {
31        return name;
32    }
33
34    public void setName(String name) {
35        this.name = name;
36    }
37
38    public String getDescription() {
39        return description;
40    }
41
42    public void setDescription(String description) {
43        this.description = description;
44    }
45}

RolePermission

 1/**
 2 * 角色权限
 3 */
 4public class RolePermission {
 5
 6    private int id;
 7
 8    private int roleId;
 9
10    private int permissionId;
11
12    public int getId() {
13        return id;
14    }
15
16    public void setId(int id) {
17        this.id = id;
18    }
19
20    public int getRoleId() {
21        return roleId;
22    }
23
24    public void setRoleId(int roleId) {
25        this.roleId = roleId;
26    }
27
28    public int getPermissionId() {
29        return permissionId;
30    }
31
32    public void setPermissionId(int permissionId) {
33        this.permissionId = permissionId;
34    }
35}

User

 1/**
 2 * 用户
 3 */
 4public class User {
 5
 6    private int id;
 7
 8    private String username;
 9
10    private Date createTime;
11
12    private String salt;
13
14
15    /**
16     * 角色集合
17     */
18    private List<Role> roleList;
19
20    public List<Role> getRoleList() {
21        return roleList;
22    }
23
24    public void setRoleList(List<Role> roleList) {
25        this.roleList = roleList;
26    }
27
28    public int getId() {
29        return id;
30    }
31
32    public void setId(int id) {
33        this.id = id;
34    }
35
36    public String getUsername() {
37        return username;
38    }
39
40    public void setUsername(String username) {
41        this.username = username;
42    }
43
44    public Date getCreateTime() {
45        return createTime;
46    }
47
48    public void setCreateTime(Date createTime) {
49        this.createTime = createTime;
50    }
51
52    public String getSalt() {
53        return salt;
54    }
55
56    public void setSalt(String salt) {
57        this.salt = salt;
58    }
59}

UserRole

 1public class UserRole {
 2
 3    private int id;
 4
 5    private int userId;
 6
 7    private int roleId;
 8
 9    public int getId() {
10        return id;
11    }
12
13    public void setId(int id) {
14        this.id = id;
15    }
16
17    public int getUserId() {
18        return userId;
19    }
20
21    public void setUserId(int userId) {
22        this.userId = userId;
23    }
24
25    public int getRoleId() {
26        return roleId;
27    }
28
29    public void setRoleId(int roleId) {
30        this.roleId = roleId;
31    }
32}

DAO 层
PermissionMapper

1public interface PermissionMapper {
2    @Select("select p.id as id, p.name as name, p.url as url from  role_permission rp " +
3            "left join permission p on rp.permission_id=p.id " +
4            "where  rp.role_id= #{roleId} ")
5    List<Permission> findPermissionListByRoleId(@Param("roleId") int roleId);
6}

RoleMapper

 1public interface RoleMapper {
 2
 3    @Select("select ur.role_id as id, " +
 4            "r.name as name, " +
 5            "r.description as description " +
 6            " from  user_role ur left join role r on ur.role_id = r.id " +
 7            "where  ur.user_id = #{userId}")
 8    @Results(
 9            value = {
10                    @Result(id=true, property = "id",column = "id"),
11                    @Result(property = "name",column = "name"),
12                    @Result(property = "description",column = "description"),
13                    @Result(property = "permissionList",column = "id",
14                    many = @Many(select = "net.xdclass.rbac_shiro.dao.PermissionMapper.findPermissionListByRoleId", fetchType = FetchType.DEFAULT)
15                    )
16            }
17    )
18    List<Role> findRoleListByUserId(@Param("userId") int userId);
19}

UserMapper

 1public interface UserMapper {
 2
 3    @Select("select * from user where username = #{username}")
 4    User findByUsername(@Param("username") String username);
 5
 6    @Select("select * from user where id=#{userId}")
 7    User findById(@Param("userId") int id);
 8
 9    @Select("select * from user where username = #{username} and password = #{pwd}")
10    User findByUsernameAndPwd(@Param("username") String username, @Param("pwd") String pwd);
11}

Service 层
UserService

 1public interface UserService {
 2
 3    /**
 4     * 获取全部用户信息,包括角色,权限
 5     * @param username
 6     * @return
 7     */
 8    User findAllUserInfoByUsername(String username);
 9
10    /**
11     * 获取用户基本信息
12     * @param userId
13     * @return
14     */
15    User findSimpleUserInfoById(int userId);
16
17    /**
18     * 根据用户名查找用户信息
19     * @param username
20     * @return
21     */
22    User findSimpleUserInfoByUsername(String username);
23}

UserServiceImpl

 1@Service
 2public class UserServiceImpl implements UserService {
 3
 4    @Autowired
 5    private RoleMapper roleMapper;
 6
 7    @Autowired
 8    private UserMapper userMapper;
 9
10    @Override
11    public User findAllUserInfoByUsername(String username) {
12        User user = userMapper.findByUsername(username);
13
14        //用户的角色集合
15        List<Role> roleList =  roleMapper.findRoleListByUserId(user.getId());
16        user.setRoleList(roleList);
17        return user;
18    }
19
20    @Override
21    public User findSimpleUserInfoById(int userId) {
22        return userMapper.findById(userId);
23    }
24
25    @Override
26    public User findSimpleUserInfoByUsername(String username) {
27        return userMapper.findByUsername(username);
28    }
29}

Controller 层
PublicController

 1@RestController
 2@RequestMapping("pub")
 3public class PublicController {
 4
 5    @Autowired
 6    private UserService userService;
 7
 8
 9    @RequestMapping("find_user_info")
10    public Object findUserInfo(@RequestParam("username")String username){
11
12        return userService.findAllUserInfoByUsername(username);
13    }
14
15}

测试

 1// http://localhost:8080/pub/find_user_info?username=jack
 2
 3{
 4  "id": 3,
 5  "username": "jack",
 6  "createTime": null,
 7  "salt": null,
 8  "roleList": [
 9    {
10      "id": 1,
11      "name": "admin",
12      "description": "普通管理员",
13      "permissionList": [
14        
15      ]
16    },
17    {
18      "id": 2,
19      "name": "root",
20      "description": "超级管理员",
21      "permissionList": [
22        {
23          "id": 1,
24          "name": "video_update",
25          "url": "/api/video/update"
26        },
27        {
28          "id": 2,
29          "name": "video_delete",
30          "url": "/api/video/delete"
31        },
32        {
33          "id": 3,
34          "name": "video_add",
35          "url": "/api/video/add"
36        },
37        {
38          "id": 4,
39          "name": "order_list",
40          "url": "/api/order/list"
41        }
42      ]
43    },
44    {
45      "id": 3,
46      "name": "editor",
47      "description": "审核人员",
48      "permissionList": [
49        {
50          "id": 1,
51          "name": "video_update",
52          "url": "/api/video/update"
53        },
54        {
55          "id": 2,
56          "name": "video_delete",
57          "url": "/api/video/delete"
58        },
59        {
60          "id": 3,
61          "name": "video_add",
62          "url": "/api/video/add"
63        }
64      ]
65    }
66  ]
67}

自定义 CustomRealm 实战
 通过 Mapper 查询数据库数据,进行校验和登录认证。

 1/**
 2 * 自定义realm
 3 */
 4public class CustomRealm extends AuthorizingRealm {
 5
 6    @Autowired
 7    private UserService userService;
 8
 9    /**
10     * 进行权限校验的时候回调用(授权)
11     * @param principals
12     * @return
13     */
14    @Override
15    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
16        System.out.println("授权 doGetAuthorizationInfo");
17        String username = (String)principals.getPrimaryPrincipal();
18        User user = userService.findAllUserInfoByUsername(username);
19
20        List<String> stringRoleList = new ArrayList<>();
21        List<String> stringPermissionList = new ArrayList<>();
22
23
24        List<Role> roleList = user.getRoleList();
25
26        for(Role role : roleList){
27            //角色集合
28            stringRoleList.add(role.getName());
29
30            List<Permission> permissionList = role.getPermissionList();
31
32            for(Permission p: permissionList){
33                if(p!=null){
34                    //权限集合
35                    stringPermissionList.add(p.getName());
36                }
37            }
38        }
39
40        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
41        simpleAuthorizationInfo.addRoles(stringRoleList);
42        simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
43        return simpleAuthorizationInfo;
44    }
45
46    /**
47     * 用户登录的时候会调用(认证)
48     * @param token
49     * @return
50     * @throws AuthenticationException
51     */
52    @Override
53    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
54
55        System.out.println("认证 doGetAuthenticationInfo");
56
57        //从token获取用户信息,token代表用户输入
58        String username = (String)token.getPrincipal();
59
60        User user =  userService.findAllUserInfoByUsername(username);
61
62        //取密码
63        String pwd = user.getPassword();
64        if(pwd == null || "".equals(pwd)){
65            return null;
66        }
67
68        return new SimpleAuthenticationInfo(username, user.getPassword(), this.getClass().getName());
69    }
70}

ShiroFilterFactoryBean 配置

  • ShiroConfig
1# 需要注入以下类型Bean
2* ShiroFilterFactoryBean (过滤器路径设置)
3* SecurityManager (环境)
4* SessionManager(CustomSessionManager)
5* CustomRealm (自定义Real)
6* HashedCredentialsMatcher (加密散列加密算法)
  • SessionManager
1# 自定CustomSessionManager需要继承以下其中一个类
2 * DefaultSessionManager: 默认实现,常用于javase  
3 * ServletContainerSessionManager: web环境  
4 * DefaultWebSessionManager:常用于自定义实现

CustomSessionManager

1import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
2public class CustomSessionManager extends DefaultWebSessionManager {
3
4}

ShiroConfig

  1import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  2import org.apache.shiro.mgt.SecurityManager;
  3import org.apache.shiro.session.mgt.SessionManager;
  4import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  5import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  6import org.springframework.context.annotation.Bean;
  7import org.springframework.context.annotation.Configuration;
  8
  9import java.util.LinkedHashMap;
 10import java.util.Map;
 11
 12@Configuration
 13public class ShiroConfig {
 14
 15    //注入 ShiroFilterFactoryBean
 16    @Bean
 17    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
 18
 19        System.out.println("执行 ShiroFilterFactoryBean.shiroFilter()");
 20
 21        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
 22
 23        //必须设置securityManager
 24        shiroFilterFactoryBean.setSecurityManager(securityManager);
 25
 26        //需要登录的接口,如果访问某个接口,需要登录却没登录,则调用此接口(如果不是前后端分离,则跳转页面)
 27        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
 28
 29        //登录成功,跳转url,如果前后端分离,则没这个调用
 30        shiroFilterFactoryBean.setSuccessUrl("/");
 31
 32        //没有权限,未授权就会调用此方法, 先验证登录-》再验证是否有权限
 33        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");
 34
 35        //拦截器路径,坑一,部分路径无法进行拦截,时有时无;因为使用的是hashmap, 无序的,应该改为LinkedHashMap
 36        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
 37
 38        //退出过滤器
 39        filterChainDefinitionMap.put("/logout","logout");
 40
 41        //匿名可以访问,也是就游客模式
 42        filterChainDefinitionMap.put("/pub/**","anon");
 43
 44        //登录用户才可以访问
 45        filterChainDefinitionMap.put("/authc/**","authc");
 46
 47        //管理员角色才可以访问
 48        filterChainDefinitionMap.put("/admin/**","roles[admin]");
 49
 50        //有编辑权限才可以访问
 51        filterChainDefinitionMap.put("/video/update","perms[video_update]");
 52
 53
 54        //坑二: 过滤链是顺序执行,从上而下,一般讲/** 放到最下面
 55
 56        //authc : url定义必须通过认证才可以访问
 57        //anon  : url可以匿名访问
 58        filterChainDefinitionMap.put("/**", "authc");
 59
 60        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
 61
 62        return shiroFilterFactoryBean;
 63    }
 64
 65    //注入 SecurityManager
 66    @Bean
 67    public SecurityManager securityManager(){
 68        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
 69        //传入 CustomRealm
 70        securityManager.setRealm(customRealm());//不能使用new,只能调用customRealm()方法返回CustomRealm
 71        //传入 SessionManager
 72        securityManager.setSessionManager(sessionManager());
 73
 74        return securityManager;
 75    }
 76
 77
 78
 79    @Bean
 80    public CustomRealm customRealm(){
 81        CustomRealm customRealm = new CustomRealm();
 82
 83        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
 84        return customRealm;
 85    }
 86
 87
 88    @Bean
 89    public HashedCredentialsMatcher hashedCredentialsMatcher(){
 90        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
 91
 92        //设置散列算法:这里使用的MD5算法
 93        credentialsMatcher.setHashAlgorithmName("md5");
 94
 95        //散列次数,好比散列2次,相当于md5(md5(xxxx))
 96        credentialsMatcher.setHashIterations(2);
 97
 98        return credentialsMatcher;
 99    }
100
101    @Bean
102    public SessionManager sessionManager(){
103        CustomSessionManager customSessionManager = new CustomSessionManager();
104        return customSessionManager;
105    }
106}

作者:Soulboy