shiro 学习笔记
1. 权限管理
1.1 什么是权限管理?
权限管理实现对用户访问系统的控制,按照安全规则或者安全策略,可以控制用户只能访问自己被授权的资源
权限管理包括用户身份认证和授权两部分,简称认证授权
1.2 什么是身份认证?
身份认证就是判断一个用户是否为合法用户的处理过程,最常用的方式就是通过核对用户输入和用户名和口令是否与系统中存储的一致,来判断用户身份是否正确
1.3 什么是授权?
授权,即访问控制,控制谁能访问哪些资源,主体进行身份认证后需要分配权限方可访问系统资源,对于某些资源没有权限是无法访问的
2. 什么是 shiro?
shiro 是一个功能强大且易于使用的 Java 安全框架,能够完成身份认证、授权、加密和会话管理等功能
2.1 shiro 的核心架构
-
Subject
主体,外部应用与 subject 进行交互,subject 记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject 在 shiro 中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过 subject 进行认证授,而 subject 是通过 SecurityManager 安全管理器进行认证授权
-
SecurityManager
安全管理器,对全部的 subject 进行安全管理,是 shiro 的核心。通过 SecurityManager 可以完成 subject 的认证、授权等,实质上 SecurityManager 是通过 Authenticator 进行认证,通过 Authorizer 进行授权,通过 SessionManager 进行会话管理。SecurityManager 是一个接口,继承了 Authenticator,Authorizer,SessionManager 这三个接口
-
Authenticator
认证器,对用户身份进行认证,Authenticator 是一个接口,shiro 提供 ModularRealmAuthenticator 实现类,通过 ModularRealmAuthenticator 基本上可以满足大多数需求,也可以自定义认证器
-
Authorizer
授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限
-
Realm
领域,相当于 datasource 数据源,securityManager 进行安全认证需要通过 Realm 获取用户权限数据,比如:如果用户身份数据在数据库,那么 realm 就需要从数据库获取用户身份信息
-
SessionManager
会话管理,shiro 框架定义了一套会话管理,它不依赖 web 容器的 session,所以 shiro 可以使用在非 web 应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录
-
SessionDAO
会话 dao,是对 session 会话操作的一套接口,比如要将 session 存储到数据库,可以通过 jdbc 将会话存储到数据库
-
CacheManager
CacheManager 即缓存管理,将用户权限数据存储在缓存,这样可以提高性能
-
Cryptography
密码管理,shiro 提供了一套加解密的组件,方便开发,比如提供常用的散列、加解密等功能
3. shiro 中的认证
3.1 认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确
3.2 shiro 认证中的关键对象
-
Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体
-
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性, 如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)
-
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等
3.3 认证流程
3.4 认证开发
3.4.1 引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
3.4.2 创建配置文件
该配置文件用来书写系统中相关的权限数据,主要用于学习使用
[users]
xiaochen=123
zhangsan=456
3.4.3 开发认证代码
public class TestAuthenticator {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.给安全管理器设置 realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3.给 SecurityUtils 全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取认证主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
// 6.认证
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.5 自定义 Realm
自定义 Realm 的实现,即是将认证/授权数据来源转为数据库的实现
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.设置自定义realm
securityManager.setRealm(new CustomerRealm());
// 3.给 SecurityUtils 全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取认证主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
// 6.认证
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义 Realm 代码实现
public class CustomerRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1. 在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 2. 查询数据库,此处模拟数据库数据
if ("xiaochen".equals(principal)) {
// 参数1:正确的用户名
// 参数2:正确的密码
// 参数3:提供当前realm的名字
SimpleAuthenticationInfo simpleAuthenticationInfo
= new SimpleAuthenticationInfo("xianchen", "123", this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
3.6 明文加密
实际使用时,我们不可能把用户密码以明文形式显示,需要做加密处理
通常的加密方式是使用 md5 + salt + hash 散列的形式,校验过程:保存盐和散列后的值,在 shiro 完成密码校验
下面是使用 shiro 提供的 api 完成加密代码示例
public class TestShiroMD5 {
public static void main(String[] args) {
// 1. 使用md5加密
// 参数1是明文密码
Md5Hash md5Hash1 = new Md5Hash("123");
// 打印加密后的密文
// 结果:202cb962ac59075b964b07152d234b70
System.out.println(md5Hash1.toHex());
// 2. 使用md5+salt加密
Md5Hash md5Hash2 = new Md5Hash("123", "X0*7ps");
// 8a83592a02263bfe6752b2b5b03a4799
System.out.println(md5Hash2.toHex());
// 3. 使用md5+salt+hash散列加密
Md5Hash md5Hash3 = new Md5Hash("123", "X0*7ps", 1024);
// e4f9bf3e0c58f045e62c23c533fcf633
System.out.println(md5Hash3.toHex());
}
}
自定义 CustomerMd5Realm
public class CustomerMd5Realm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1. 在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
// 2. 查询数据库,此处模拟数据库数据
if ("xiaochen".equals(principal)) {
// 参数1:正确的用户名
// 参数2:正确的密码
// 参数3:提供当前realm的名字
// md5
// return new SimpleAuthenticationInfo(principal, "202cb962ac59075b964b07152d234b70", this.getName());
// md5+salt
/*return new SimpleAuthenticationInfo(principal, "8a83592a02263bfe6752b2b5b03a4799", ByteSource.Util.bytes("X0*7ps"), this.getName());*/
}
return null;
}
}
校验流程
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
// 1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.设置自定义realm
CustomerMd5Realm realm = new CustomerMd5Realm();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 使用MD5加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
securityManager.setRealm(realm);
// 3.给 SecurityUtils 全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取认证主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
// 6.认证
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. shiro 中的授权
4.1 授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的
4.2 关键对象
授权可简单理解为 who 对 what(which) 进行 How 操作:
- Who,即主体(Subject),主体需要访问系统中的资源
- What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为 t01 的商品为资源实例,编号为 001 的商品信息也属于资源实例
- How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为 001 用户的修改权限等,通过权限可知道主体对哪些资源都有哪些操作许可
4.3 授权方式
基于角色的访问控制,以角色为中心进行访问控制
if(subject.hasRole("admin")){
//操作什么资源
}
基于资源的访问控制,以资源为中心进行访问控制
if(subject.isPermission("user:update:01")){ //资源实例
//对01用户进行修改
}
if(subject.isPermission("user:update:*")){ //资源类型
//对01用户进行修改
}
4.4 权限字符串
权限字符串的规则是:资源标识符:操作:资源实例标识符
,意思是对哪个资源的哪个实例具有什么操作,:
是资源/操作/实例的分割符,权限字符串也可以使用 *
通配符
例子:
- 用户创建权限:
user:create
,或user:create:*
- 用户修改实例 001 的权限:
user:update:001
- 用户实例 001 的所有权限:
user:*:001
4.5 授权编程实现
在之前 md5 加密的基础上,实现授权操作
自定义 CustomerMd5Realm
public class CustomerMd5Realm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
// 根据身份信息(用户名)从数据库获取当前用户的角色以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 将数据库中查询的角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
// 将数据库中查询的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create:02");
return simpleAuthorizationInfo;
}
...
}
授权逻辑
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
...
// 7. 认证用户进行授权
if (subject.isAuthenticated()) {
// 7.1 基于角色权限控制
boolean hasRole = subject.hasRole("admin");
System.out.println("角色校验:" + hasRole);
// 7.2 基于多角色权限控制
boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("admin", "user"));
System.out.println("多角色校验:" + hasAllRoles);
// 7.3 是否具有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
// 7.4 基于权限字符串的访问控制
boolean permitted = subject.isPermitted("user:*:01");
System.out.println("资源权限校验:" + permitted);
// 7.5 分布具有哪些资源权限
boolean[] permitted1 = subject.isPermitted("user:*:01", "order:*:10");
for (boolean b : permitted1) {
System.out.println(b);
}
// 7.6 同时具有哪些资源权限
boolean permittedAll = subject.isPermittedAll("user:*:01", "product:*");
System.out.println("多资源权限校验:" + permittedAll);
}
}
}