shiro
shiro-实战
权限管理
-
什么是权限管理
基本上涉及到用户参与的系统都要进行权限管理,
权限管理属于系统安全的范畴, 权限管理实现对用户访问系统的控制, 按照安全规则或者 安全策略
控制用户可以访问而且只能访问自己被授权的资源权限管理包括用户
身份认证
和授权
两部分,简称 认证授权
,对于需要访问控制的资源用户首先要经过身份认证, 认证通过后具有该资源的访问权限方可访问 -
什么是身份认证
身份认证
就是判断一个用户是否为合法用户的处理过程,最常用的简单身份认证方式是系统通过核对输入的用户名和口令, 看其是否与系统中存储的该用户的用户名和口令一致, 来判断用户身份是否正确, 对于采用指纹等系统, 则出示指纹, 对于硬件 key
等刷卡系统, 则需要刷卡 -
什么是授权
授权、即访问控制
控制谁能访问那些资源,主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法进行访问的
shiro
-
架构图
架构图 (官网) -
解释
-
Subject
Subject
,即主体 外部应用与 subject
进行交互, subject
记录了当前操作用户, 将用户的概念理解为当前操作的主体, 可能是一个通过浏览器请求的用户, 也可能是一个运行的程序, Subject
在 shiro
中是一个接口, 接口中定义了很多认证授权相关的方法, 外部程序通过 subject
进行认证授权,而 subject
是通过 SecurityManager
安全管理器进行认证授权 -
SecurityManager
SecurityManager 即安全管理器
,对外部的 subject
进行安全管理, 它是 shiro
的核心, 负责对所有的 subject
进行安全管理, 通过 SecurityManager
可以完成 subject
的认证、授权等,实质上 SecurityManager
是通过 Authenticator
进行认证, 通过 Authorizer
进行授权, 通过 SessionManager
进行会话管理等 SecurityManager
是一个接口,继承了 Authenticator、Authorizer、SessionManager
这三个接口 -
Authenticator
Authenticator
即认证器,对用户身份进行认证, Authenticator
是一个接口, shiro
提供 ModularRealmAuthenticator
实现类, 通过 ModularRealmAuthenticator
基本上可以满足大多数需求, 也可以自定义认证器 -
Authorizer
Authorizer
用户通过认证器认证通过,即授权器 在访问功能时需要通过授权器判断用户是否具有此功能的操作权限 -
Realm
Realm
相当于即领域 datasource
数据源, securityManager
进行安全认证需要通过 Realm
获取用户权限数据, 比如: 如果用户身份数据在数据库那么 realm
就需要从数据库获取用户身份信息 注意: 不要把
realm
理解成只是从数据源取数据, 在 realm
中还有认证授权校验的相关的代码 -
SessionManager
SessionManager
,即会话管理 shiro
框架定义了一套会话管理, 他不依赖 web
容器的 session
可以使用在非 web
应用上, 也可以将分布式应用的会话集中在一点管理, 此特性可以使他实现单点登录 -
SessionDAO
SessionDAO
即会话 dao 是对 session
会话操作的一套接口, 比如要将 session
存储到数据库, 可以通过 jdbc
将会话存储到数据库 -
CacheManager
CacheManager
将用户权限数据存储在缓存,这样可以提高性能即缓存管理 -
Cryptography
Cryptography
即密码管理, shiro
提供了一套加密 / 解密的组件, 方便开发, 比如提供常用的散列, 加 / 解密等功能
-
shiro 中的认证
-
认证
身份认证,
就是判断一个用户是否为合法用户的处理过程, 最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令, 看其是否与系统中存储的该用户的用户名和口令是否一致, 来判断用户身份是否正确 -
shiro
中认证的关键对象 -
Subject
主体 访问系统的用户,
程序等、进行认证的都称为主体 -
Principal
身份信息 是主体进行身份认证的标识,
标识必须具有 唯一性
,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份 -
credential
凭证信息 是只有主体自己知道的安全信息,
如密码、证书等
-
-
认证流程
认证流程 -
认证的开发
-
创建项目
( maven-archetype-quickstart
)并引入依赖 1
2
3
4
5<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency> -
引入
shiro
配置文件并加入如下配置 1
2
3# 临时数据
[users]
coderitl=root -
开发认证代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37package com.coderitl.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
public class TestAuthenticator {
public static void main(String[] args) {
// 1. 创建安全管理器 => SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2. 给安全管理器设置 realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3. SecurityUtils 给安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4. 关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
// 5. 创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("coderitl", "root");
try {
// 用户认证
subject.login(token);
// true => 认证通过
System.out.println("认证状态: " + subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("认证失败: 用户名不存在~");
} catch (IncorrectCredentialsException e) {
System.out.println("认证失败: 密码错误~");
}
}
} -
源码实现
-
根据认证源码认证使用的是
SimpleAccountRealm
SimpleAccountRealm
-
自定义
realm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package com.coderitl.shiro.realm;
import jdk.nashorn.internal.parser.Token;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
// 自定义 realm 实现 将认证/ 授权数据来源转为数据库的实现
public class CustomerRealm extends AuthorizingRealm {
// 1. => 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证...............");
// 1. 在 token 中获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 根据身份信息使用 jdbc | mybatis 查询相关数据库
if ("coderitl".equals(principal)) {
// SimpleAuthenticationInfo 的属性可以赋值用户名和密码
// protected PrincipalCollection principals; 用户名
// protected Object credentials; 密码
// 参数1: 返回数据库中正确的用户名 参数 2: 返回数据库中正确的密码 参数 3: 提供当前 realm 的名称 (底层生成) => this.getName() 获取
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("coderitl","root",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
// 2. => 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权...............");
return null;
}
}-
测试
自定义
realm 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package com.coderitl.shiro;
import com.coderitl.shiro.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class TestCustomerRealm {
public static void main(String[] args) {
// 创建 SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 设置自定义的 realm
defaultSecurityManager.setRealm(new CustomerRealm());
// 将安全工具类设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 通过安全工具类获取 subject
Subject subject = SecurityUtils.getSubject();
// 创建 token
UsernamePasswordToken token = new UsernamePasswordToken("coderitl", "root");
try {
// 用户认证
subject.login(token);
System.out.println("认证状态: " + subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("认证失败: 用户名不存在~");
} catch (IncorrectCredentialsException e) {
System.out.println("认证失败: 密码错误~");
}
}
}
-
-
-
加密
-
md5
和 Salt
实际应用是将盐和散列后的值存在数据库中,
自动 realm
从数据库取出盐和加密后的值由 shiro
完成密码校验 -
md5
作用: 一般用来加密 或者 签名 -
特点:
MD5
算法不可逆, 如果内容相同无论执行多少次 md5
生成结果始终是一致 -
基础使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void shouldAnswerWithTrue() {
// 不能使用 setBytes => 未实现 md5 加密
Md5Hash md5Hash1 = new Md5Hash();
md5Hash1.setBytes("root".getBytes());
System.out.println("md5Hash1 = " + md5Hash1);
// 使用构造 => 实现 md5 加密
Md5Hash md5Hash2 = new Md5Hash("root".getBytes());
System.out.println("md5Hash2 = " + md5Hash2);
// md5 + salt
Md5Hash md5Hash3 = new Md5Hash("root".getBytes(), "coderitl");
System.out.println("md5Hash3 = " + md5Hash3);
// md5 + salt + hash散列 (最终用法)
Md5Hash md5Hash4 = new Md5Hash("root".getBytes(), "coderitl", 1024);
System.out.println("md5Hash4 = " + md5Hash4);
}
-
-
综合使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package com.coderitl.shiro;
import com.coderitl.shiro.realm.CustomerMDSalt;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class TestCustomerMD5Salt {
public static void main(String[] args) {
// 创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 注入 realm
CustomerMDSaltHash realm = new CustomerMDSaltHash();
// 设置 realm 使用 hash 凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置使用的 md5 算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);
// 将 安全管理器注入安全工具
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 通过安全工具类获取 subject
Subject subject = SecurityUtils.getSubject();
// 认证
UsernamePasswordToken token = new UsernamePasswordToken("coderitl", "root");
try {
// 用户认证
subject.login(token);
System.out.println("登录成功~");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("认证失败: 用户名不存在~");
} catch (IncorrectCredentialsException e) {
System.out.println("认证失败: 密码错误~");
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package com.coderitl.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class CustomerMDSaltHash extends AuthorizingRealm {
// 1. => 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证...............");
// 1. 获取身份信息
String principal = (String) authenticationToken.getPrincipal();
// 根据用户名查询数据库
if ("coderitl".equals(principal)) {
return new SimpleAuthenticationInfo(
// 数据库中的用户名
"coderitl",
// 数据库中的密码
"6d0b13265ab36f227e1341c97a3fe4f1",
// 随机盐
ByteSource.Util.bytes("coderitl"),
// realm 名称
this.getName());
}
return null;
}
// 2. => 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权...............");
return null;
}
}凭证解析器
-
shiro 中的授权
-
授权
授权,即访问控制,
控制谁能访问那些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的 -
关键对象
授权可简单理解为
who
对 what(which)
进行 How
操作 who
即主体 (Subject) 主体需要访问系统中的资源 what
即资源 (Resource) 如系统菜单, 页面,按钮、类方法、系统商品信息等, 资源包括资源类型和资源实例, 比如商品信息为资源类型, 类型为 51010
的商品为资源实例, 变为为 001
的商品信息也属于资源实例 How
规定了主体对资源的操作许可,权限 / 许可 权限离开资源没有意义, 如用户查询权限,用户添加权限,某个类方法的调用权限,编号为 001
用户的修改权限等,通过权限可知主体对那些资源操作许可 -
授权流程
授权流程 -
授权的方式
- 基于角色的访问控制
RBAC
基于角色的访问控制,是以角色为中心进行访问控制
- 基于资源的访问控制
RBAC
基于资源的访问控制,是以资源为中心进行访问控制
- 基于角色的访问控制
-
权限字符串
权限字符串的规则是:
资源标识符:
意思是对那个资源的那个实例具有什么操作操作: 资源实例标识符 :
是资源 / 操作 / 实例的分隔符, 权限字符串也可以使用 *
通配符 例:
- 用户创建权限
user:create | user:create:*
- 用户修改实例
001
的权限: user:update:001
- 用户实例
001
的所有权限: user:*:001
- 用户创建权限
-
授权编程实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 自定义的
// 2. => 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// PrincipalCollection principalCollection => 身份的集合
System.out.println("授权...............");
// 主身份用户信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("用户名: " + primaryPrincipal);
// 根据身份信息(用户名) 获取当前用户的角色信息以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 将数据库中查询角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("root");
// 将数据库中查询权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
return simpleAuthorizationInfo;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 认证后 => 授权
if (subject.isAuthenticated()) {
// 1. (单一权限)基于角色的权限控制
System.out.println("单一角色权限: " + subject.hasRole("admin"));
System.out.println("******************************");
// 2. 基于多角色的权限控制
System.out.println("多角色权限: " + subject.hasAllRoles(Arrays.asList("admin", "root")));
System.out.println("<------------------------------------------>");
// 3. 是否具有某一角色权限
boolean[] booleans = subject.hasRoles(Arrays.asList("root", "admin", "super"));
for (boolean aBoolean : booleans) {
System.out.println("aBoolean = " + aBoolean);
}
System.out.println("=================================");
// 基于权限字符串的访问控制
System.out.println(subject.isPermitted("user:*:*"));
// 分别具有那些权限
boolean[] permitted = subject.isPermitted("user:*01", "order:*:01");
for (boolean b : permitted) {
System.out.println(b);
}
// 同时具有那些权限
boolean permittedAll = subject.isPermittedAll("user:*01", "order:*:01");
System.out.println("permittedAll = " + permittedAll);
}
Springboot 整合
整合思路
-
流程
流程
环境搭建
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- springboot 整合 shiro 依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- 整合 jsp -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies> -
创建
shiro
配置 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61package com.coderitl.shiro.config;
import com.coderitl.shiro.realms.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
public class ShiroConfig {
// 1. 创建 shiroFilter
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 给 filter 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 配置系统受限资源
// 配置系统公共资源
Map<String, String> map = new HashMap<>();
// 参数一: 受限资源路径 参数二: 限制级别 authc=> 代表这个资源需要认证和授权
map.put("/user/**", "anon"); // 所有 /user 为 anon =>不认证,可直接访问
map.put("/register.jsp", "anon");
map.put("/", "authc");
map.put("/index.jsp", "authc");
// 默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
// 2. 创建安全管理器
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 设置 realm
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
// 3. 创建自定义 realm
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
// 修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置加密算法为 md5
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return customerRealm;
}
}-
常见过滤器
配置缩写 对应的过滤器 功能 anno
AnonymousFilter
指定 url
可以匿名访问 authc
FormAuthenticationFilter
指定 url
需要 form
表单登录,默认会从请求中获取 username,password,remeberMe
等参数并尝试登录, 如果登陆不了就会跳转到 loginURL
配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑,自己写的话出错可以定制信息 authcNasic
BasicHttpAuthenticationFilter
指定 url
需要 basic
登录 logout
LogoutFilter
退出过滤器, 配置指定的 url
就可以实现推出功能, 非常方便 noSessionCreation
NoSessionCreationFilter
禁止创建会话 perms
HttpMethodPermissionFilter
需要指定的权限才能访问 port
PortFilter
需要指定的端口才能访问 rest
HttpMethodPermissionFilter
将 http
请求方法转换成相应的动词来构造一个权限字符串,意义不大 roles
RolesAuthorizationFilter
需要指定角色才能访问 ssl
SslFilter
需要 https
请求才能访问 user
UserFilter
需要已登录或 记住我
的用户才能访问
-
-
创建自定义
realms
(认证 、授权
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44package com.coderitl.shiro.realms;
import com.coderitl.shiro.dao.UserDao;
import com.coderitl.shiro.entity.User;
import com.coderitl.shiro.service.UserService;
import com.coderitl.shiro.service.impl.UserServiceImpl;
import com.coderitl.shiro.utilos.ApplicationContextUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
public class CustomerRealm extends AuthorizingRealm {
// 1. => 认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证...............");
String principal = (String) authenticationToken.getPrincipal();
// 在工厂中获取 service 对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
// 获取数据库对象
User user = userService.findUserByUserName(principal);
// 设置 md5 + salt + hash
if (!ObjectUtils.isEmpty(user)) {
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
return null;
}
// 2. => 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权...............");
return null;
}
}-
授权
-
编程式
1
2
3
4
5
6Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")){
// 有 admin 角色权限时执行该逻辑
}else{
// 无该角色权限
} -
注解式
1
2
3
4
5
6
7
8
9// 角色
public void hello(){
// 有 admin角色权限时执行该逻辑
}
// 权限字符串 -
标签式
1
2
3
4<shiro:hasRole name="admin">
<!-- 有权限 -->
</shiro:hasRole>注意: Thymeleaf 中使用 shiro 需要额外集成!
-
-
编码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 自定义 realm => 假数据模拟
// 2. => 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权...............");
// 获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
// 根据主身份信息获取角色 和 权限
if("coder-itl".equals(primaryPrincipal)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 添加角色权限
simpleAuthorizationInfo.addRole("admin");
return simpleAuthorizationInfo;
}
return null;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!-- 添加 tag -->
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<ul>
<li><a href="#"> 用户管理
<shiro:hasRole name="admin">
<li><a href="#"> 商品管理
<li><a href="#"> 订单管理
<li><a href="#"> 物流管理
</shiro:hasRole>
</ul>
<!-- 拥有任意角色权限可访问 -->
<shiro:hasAnyRoles name="user,admin">
<li><a href="#"> 用户管理
</shiro:hasAnyRoles>-
访问
受限资源
-
-
权限字符串
1
2
3
4
5// 添加角色权限
simpleAuthorizationInfo.addRole("user");
// realm 权限字符串
simpleAuthorizationInfo.addStringPermission("user:find:*");
simpleAuthorizationInfo.addStringPermission("user:delete:*");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<shiro:hasAnyRoles name="user,admin">
<li>
<a href="#"> 用户管理
<ul>
<shiro:hasPermission name="user:delete:*">
<li><a href=""> 删除 </a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href=""> 修改 </a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href=""> 查询 </a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>-
访问
权限字符串
-
-
-
创建工具类
-
获取随机盐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.coderitl.shiro.utilos;
import java.util.Random;
/**
* 生成随机盐
*/
public class SaltUtils {
public static String getSalt(int n) {
// TODO: 快速转换大小写快捷键 ctrl + shift + U
char[] chars = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890!@#$%^&*&()_[];',./|".toCharArray();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < n; i++) {
char aChar = chars[new Random().nextInt(chars.length)];
stringBuilder.append(aChar);
}
return stringBuilder.toString();
}
} -
获取实例对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.coderitl.shiro.utilos;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
// 根据 bean 名字获取工厂中指定 bean 对象
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
-
-
数据库实体
1
2
3
4
5
6
7
8
9
10
11
public class User {
private Integer id;
private String username;
private String password;
private String salt;
} -
dao
1
2
3
4
5
6
7
public interface UserDao {
void save(User user);
User findUserByUserName(String username);
} -
service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package com.coderitl.shiro.service.impl;
import com.coderitl.shiro.dao.UserDao;
import com.coderitl.shiro.entity.User;
import com.coderitl.shiro.service.UserService;
import com.coderitl.shiro.utilos.SaltUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void register(User user) {
// 生成随机盐
String salt = SaltUtils.getSalt(8);
// 将随机盐保存到数据
user.setSalt(salt);
// 明文密码加密 + salt + hash
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
// 处理业务调用 dao
userDao.save(user);
}
public User findUserByUserName(String username) {
return userDao.findUserByUserName(username);
}
} -
创建控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64package com.coderitl.shiro.controller;
import com.coderitl.shiro.entity.User;
import com.coderitl.shiro.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
public class UserController {
private UserService userService;
public String login(String username, String password) {
log.info("username: [{}],password: [{}]", username, password);
// 获取主体对象 web 环境会自动注入
Subject subject = SecurityUtils.getSubject();
try {
// 用户认证
subject.login(new UsernamePasswordToken(username, password));
log.info("认证状态: {}", subject.isAuthenticated());
// 重定向到 index.jsp
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
log.info("认证失败: 用户名不存在~");
} catch (IncorrectCredentialsException e) {
log.info("认证失败: 密码错误~");
}
// 失败回到登录页面
return "redirect:/login.jsp";
}
// 退出
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
public String register(User user) {
log.info("user: [{}]", user.toString());
try {
userService.register(user);
return "redirect:/login.jsp";
} catch (Exception e) {
e.printStackTrace();
return "redirect:/register.jsp";
}
}
} -
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30server:
port: 8888
servlet:
context-path: /shiro # context-path: 必须 /xxx 开头
# 配置视图解析器
spring:
application:
name: shiro
mvc:
view:
prefix: / # 前缀
suffix: .jsp # 后缀
# 数据源
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro?autoReconnect=true&useSSL=false&characterEncoding=utf-8
username: root
password: root
initial-size: 1
min-idle: 1
max-active: 20
mybatis:
type-aliases-package: com.coderitl.shiro.entity # 实体类所在包名
mapper-locations: classpath:/mapper/*.xml # mybatis 映射文件所在路径
# 配置自定义包的日志级别
logging:
level:
com.coderitl.shiro.dao: debug -
授权数据持久化
-
数据库设计
-
用户 ->
角色 -> 权限 -> 资源 ( *
)ER
-
用户 -> 角色
-
用户 -> 权限
-
用户 -> 角色
-
用户 -> 权限
-
-
权限表设计
权限表设计 -
授权持久化实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 2. => 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权...............");
// 获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
// 根据主身份信息获取角色 和 权限
// 在工厂中获取 service 对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
User user = userService.findRoleUserByUserName(primaryPrincipal);
// 授权角色信息
if (!CollectionUtils.isEmpty(user.getRoles())) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role -> {
// 添加角色信息
simpleAuthorizationInfo.addRole(role.getName());
// 添加权限信息
List<Perms> perms = userService.findPermsByRoleId(role.getId());
if (!CollectionUtils.isEmpty(perms)) {
perms.forEach(perm -> {
simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
-
shiro 整合 SpringBoot 缓存之 EhCache 实现
-
Cache
作用 -
Cache
缓存: 计算机内存中一段数据 -
作用: 用来减轻
DB
的访问压力, 从而提高系统的查询效率 -
流程
流程 -
使用
shiro
中默认 EhCache
实现缓存 -
引入依赖
1
2
3
4
5
6<!-- 默认缓存: shiro + EhCache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency> -
开启缓存,
自定义 realm
中开启 1
2
3
4
5
6
7
8
9
10
11
12
13// 开启缓存管理器
customerRealm.setCacheManager(new EhCacheManager());
// 开启全局缓存
customerRealm.setCachingEnabled(true);
// 开启认证缓存
customerRealm.setAuthenticationCachingEnabled(true);
// 设置认证缓存名称
customerRealm.setAuthenticationCacheName("authenticationCache");
// 开启授权缓存
customerRealm.setAuthorizationCachingEnabled(true);
// 设置授权缓存名称
customerRealm.setAuthorizationCacheName("authorizationCache");
-
-
整合 Redis
-
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> -
实体类添加序列化
-
缓存管理器
-
自定义缓存管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.coderitl.shiro.cache;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
// 自定义 shiro 缓存管理器
public class RedisCacheManager implements CacheManager {
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
log.info("cacheName: [{}]", cacheName);
return new RedisCache<K, V>(cacheName);
}
} -
实现
Cache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94package com.coderitl.shiro.cache;
import com.coderitl.shiro.utilos.ApplicationContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Collection;
import java.util.Set;
// 自定义 redis 缓存
public class RedisCache<k, v> implements Cache<k, v> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
public v get(k k) throws CacheException {
log.info("redis get use.............");
log.info("get Key: [{}]", k);
// return (v) getRedisTemplate().opsForValue().get(k.toString());
// hash
return (v) getRedisTemplate().opsForHash().get(this.cacheName, k.toString());
}
public v put(k k, v v) throws CacheException {
log.info("redis put use.............");
log.info("put Key: [{}]", k);
log.info("put Value: [{}]", v);
// getRedisTemplate().opsForValue().set(k.toString(), v);
getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v);
return null;
}
public v remove(k k) throws CacheException {
log.info("redis remove use.............");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName, k.toString());
}
public void clear() throws CacheException {
log.info("redis clear use.............");
// 删除 hash => Map<K,Map<k,v>>
getRedisTemplate().delete(this.cacheName);
}
public int size() {
log.info("redis size use.............");
// size() => long => intValue => int
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
public Set<k> keys() {
log.info("redis keys use.............");
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
public Collection<v> values() {
log.info("redis values use.............");
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate() {
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// 序列化 k
redisTemplate.setKeySerializer(new StringRedisSerializer());
// hash 的序列化 k
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
-
-
自定义
salt
1
2
3
4
5
6
7
8// 自定义 salt 实现,
实现序列化接口 => 基础使用
public class MyByteSource extends SimpleByteSource implements Serializable {
public MyByteSource(String str) {
super(str);
}
}
// 更换 md5 处的 SourceBytes -
更换缓存管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// 3. 创建自定义 realm
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
// 修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置加密算法为 md5
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
// 开启缓存管理器 => redis
customerRealm.setCacheManager(new RedisCacheManager());
// 开启全局缓存
customerRealm.setCachingEnabled(true);
// 开启认证缓存
customerRealm.setAuthenticationCachingEnabled(true);
// 设置认证缓存名称
customerRealm.setAuthenticationCacheName("authenticationCache");
// 开启授权缓存
customerRealm.setAuthorizationCachingEnabled(true);
// 设置授权缓存名称
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
} -
标签
1
2<!-- 获取主体信息 -->
登录用户: <shiro:principal/>