shiro
1.Shiro概述
1.1 Shiro是什么
Shiro是一个非常强大的、易于使用的、开源的、权限框架。它包括了权限校验、权限授予、会话管理、安全加密等组件。
1.2 为什么需要使用Shiro
如果你是需要设计RBAC(Role Based Access Control)基础系统,需要编写大量用于权限控制的代码时。那么你需要使用Shiro。因为Shiro已经将RBAC系统大量的代码封装好,可以减少我们大量的工作量。
1.3 Shiro需要的jar包
1.4 Shiro结构图
Authentication:权限校验,每次操作校验用户是否有访问权限
Authorization:授权,用户登录时,授予用户对应的权限
Session Management:会话管理,用于记录用户的登录状态
Cryptography:加密,加密算法的实现(SHA、MD5)
web Support:对Web项目的支持,Shiro的标签!!
2.Shiro入门
2.1.访问流程图
1.首先应用访问(可以使用远程调用,可以是Web请求等),Shiro通过一个Subject对象来标识当前访问的身份。这句话告诉我们,第一次访问的时候,Shiro肯定会创建一个Subject对象标签当前请求(用户)的身份。
2.SecurityManger容器创建一个Subject对象验证请求的参数,SecurityManager的作用是统一管理Subject。这句话意味着,一个SecurityManager对象管理多个Subject的对象。
3.Subject通过SecurityManger获得操作当前用户的权限,在启动的那一刻,SecurityManger就会加载shiro.ini权限配置文件,在用户登录成功后,可以根据shiro配置的信息,获得用户对应的权限。
shiro配置:是一个权限控制信息文件,里面必须包括用户的验证信息,权限的信息
2.2 .配置步骤说明
根据访问流程图,我们要使用访问Shiro权限框架的功能。首先需要有一个配置文件shiro.ini配置文件配置了用户的权限认证信息。然后通过SessionManager对象读取配置文件,获得用户身份Subject,
客户端代码通过当前用户访问有权限的操作。
由此,得出配置步骤:
第一步:任何框架都需要导入包
第二步:创建一个shiro.ini配置文件。(文件名任意编写,后缀必须为ini)
第三步:编写测试代码
2.3.配置步骤
2.3.1.第一步:导入包
2.3.2.第二步:shiro.ini配置文件
创建一个shiro.ini配置,编写权限认证信息。
注意事项:
1.shiro.ini文件名可以任意编写,但后缀必须是ini
shiro.ini配置文件放在classpath根目录
shiro.ini规则说明
在这里插入代码片
[users]
jim = jim,admin
[roles]
admin = *
2.3.3.第三步:创建SecurityManager对象
package cn.gzsxt.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
public class ShiroDemo {
public static void main(String[] args) {
// 1.获得SecurityManager对象
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.createInstance();
//2.设置SecurityUtils使用的安全管理器是securityManager
SecurityUtils.setSecurityManager(securityManager);
//2.获得subject
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token=new UsernamePasswordToken("jim", "jim");
//3.校验用户名密码是否正确
try {
Subject resultSubject = securityManager.login(subject, token);
//获得用户名
System.out.println(resultSubject.getPrincipal());
//判断是否拥有admin角色
boolean hasRole = resultSubject.hasRole("admin");
System.out.println(hasRole);
} catch (AuthenticationException e) {
System.out.println("校验失败,用户名或者密码不正确");
e.printStackTrace();
}
}
}
3.Realm的使用
3.1.Realm机制的必要性
以上案例,我们看到,我们的用户验证信息来自于配置文件的[users]以及[roles]标记。这样的难以符合我们实际的需求。
我们希望可以将Shiro校验的用户信息存储在数据库里面,在从数据库里面读取出来。
解决方案:Shiro是通过Realm机制,实现将配置文件的校验用户信息存放在数据库、LDAP等数据存储系统里面。
说白了,现在我们要做的事情,就是从数据库里面获得用户的验证信息!!!
3.2.访问流程图说明
如图所示:
1.我们需通过Subject封装访问用户的信息
2.我们需要一个SecurityManager对象来管理所有用户的权限
3.我们需要ini配置文件配置获得Realm对象
4.我们需要在Realm进行权限验证以及授权
3.3.配置步骤说明
第一步:导入包
第二步:创建shiro.ini配置文件
第三步:创建入口的测试类对象
第四步:创建Realm对象
第五步:配置shiro.ini调用Realm对象
3.4.权限校验-配置入门
3.4.1.第一步:导入包
3.4.2.第二步:创建shiro.ini配置文件
3.4.3.第三步:编写一个测试类
package cn.gzsxt.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class PermissionTest {
@Test
public void authc(){
try {
//第一步:获得SecurityManager对象
IniSecurityManagerFactory ismf=new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = ismf.createInstance();
//第二步:构造一个subject
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//第三步:封装用户名密码(身份信息)
UsernamePasswordToken token=new UsernamePasswordToken("zhangsan", "123456");
//第四步:校验(登录)
Subject resultSubject = securityManager.login(subject, token);
//第五步:验证是否通过
System.out.println(resultSubject.isAuthenticated());
} catch (AuthenticationException e) {
e.printStackTrace();
}
}
}
第四步:编写Realm
package cn.gzsxt.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;
/**
* 自动一个MyRealm类,用于权限验证以及权限授予
*
* @author ranger
*
*/
public class MyRealm extends AuthorizingRealm {
/**
* 权限验证 所谓的权限验证,就是验证访问者(subject).是否使用有使用权限的身份。 说白了,就验证用户名密码是否正确
*
* 如果校验成功,我们就返回AuthenticationInfo对象 如果校验失败,我们需要返回一个异常
* UnknownAccountException
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("用户名:" + token.getPrincipal());
if (token.getPrincipal().equals("zhangsan")) {
return new SimpleAuthenticationInfo(token.getPrincipal(), "123456",
this.getName());
} else {
return null;
}
}
/**
* 授权 根据通过校验的身份(subject),说白了就是登录成功的访问者,我们给予什么权限。
* 将查询到的权限信息封装在AuthorizationInfo里面返回!!
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) {
return null;
}
}
3.4.5.加密
需求:我们的密码是明文的,我们需要将密码Md5加密。
问题:我们发现我们自己写的Md5的类,无法传递给SimpleAuthenticationInfo对象,作为密码校验。如何解决的这个问题呢?
答:shiro框架自带的密码加密的功能。
1.SimpleHash类:用于生成指定的Hash算法。
2.HashedCredentialsMatcher类:用于让Realm校验时,校验指定的Hash算法
3.ByteSource 用于给Hash算法加盐的
4.–创建Md5密码
package cn.gzsxt.test;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
//注意事项:设置的创建密码的参数必须与校验器的参数保持一致
public class CreatePasswordUtils {
public static void main(String[] args) {
//加密方式
String hashAlgorithmName = "MD5";
//明文密码
Object credentials = "123456";
//盐值
Object salt = ByteSource.Util.bytes("nchu234we");
//迭代加密的次数,
int hashIterations = 1;
//返回加密的结果
Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
//加密后的密码
System.out.println(result);
}
}
5.修改配置文件
[main]
# 对象名 =类全限制名 ,就是创建一个对象
myRealm = cn.gzsxt.realm.MyRealm
#加密的对象
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#设置加密的算法为md5 ,属性对应的是set方法
credentialsMatcher.hashAlgorithmName=md5
#设置md5加密次数为1次
credentialsMatcher.hashIterations=1
#设置md5算法是加盐
credentialsMatcher.hashSalted=true
#将加密对象指定给对应给myRealm对象
myRealm.credentialsMatcher=$credentialsMatcher
#将realm对象加入到securityManager容器里面
#引用 securityManager.realms = $对象名
#securityManager对象名是shiro固定的。用于指定securityManager容器
#对象的引用格式为: 对象名.属性名 = $对象名;
securityManager.realms = $myRealm
6.修改校验代码
package cn.gzsxt.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;
/**
* Realm :所有Realm的父接口
* AuthenticatingRealm :只有权限校验,没有权限授权的Realm
* AuthorizingRealm : 既有权限校验,权限授权的Realm (主要就是使用它)
* JdbcRealm:使用实现的对数据库 操作的代码的Realm。它已经写死了表名,必要要按它定义的表名定义数据库表。(灵活性差)
*
* 注意事项:使用Realm,将验证认证信息与获得授权信息,都放在一起!!!!!
* @author ranger
*
*
*/
public class MyRealm extends AuthorizingRealm {
/**
* 用于权限校验
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
try {
System.out.println("=权限校验=");
//第一步:获得请求过来的用户名
Object principal = token.getPrincipal();
//第二步:根据用户查询数据库,返回用户信息 (模拟数据,查询到了"admin")
if("admin".equals(principal)){
//第三步:如果用户名相同,校验密码,如果密码正确,不会报异常,如果校验不通过就报异常
//参数1:用户名
//参数2:校验的密码,从数据库查询出来的
//参数3:realm的名字 。随便设置一个唯一字符串
//4). md5加密盐值
ByteSource salt = ByteSource.Util.bytes("nchu234we");
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,"8763d15228c560fed665e1fe73b2f601",salt,this.getName());
return authenticationInfo;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 权限授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
return null;
}
}
3.5.权限授予-配置入门
3.5.1.第一步:检验Subject对象的权限校验方法
package cn.gzsxt.test;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
/**
- 注意:根据示例
- 1.身份信息是放在Subject这个身份对象里面的
- 2.Shiro的所有对象是通过securityManager来管理
- 3.可以在配置文件配置,认证的信息(数据库里面的表数据)
- @author ranger
*/
public class ShiroTest {
public static void main(String[] args) {
//第一步:读取配置文件
IniSecurityManagerFactory factory= new IniSecurityManagerFactory("classpath:shiro.ini");
//第二步:获得安全管理器
SecurityManager securityManager = factory.createInstance();
//第三步:通过安全管理器帮助类构建一个Subject(身份)
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//第四步:构建身份信息(用户名/密码)
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
//第五步:校验是否成功,将一个空的身份,以及身份信息通过登录方法,如果成功返回带身份信息的Subject对象。如果不成功就报异常
try {
Subject result = securityManager.login(subject, token);
System.out.println("认证成功,用户名"+result.getPrincipals());
boolean flag = result.isAuthenticated();
//使用校验方法,校验用户对应的角色
boolean hasRole = result.hasRole("roleAdmin");
System.out.println("是否有指定的角色:"+hasRole);
List<String> hasRoles=new ArrayList<>();
hasRoles.add("roleAdmin");
hasRoles.add("roleEdu");
boolean allRoles = result.hasAllRoles(hasRoles);
System.out.println("是否都包括集合的所有角色:"+allRoles);
//校验用户是否有对应的权限
boolean permitted = result.isPermitted("user:add");
System.out.println("是否有user:add权限:"+permitted);
boolean permitted2 = result.isPermitted("modular:add");
System.out.println("是否有modular:add权限:"+permitted2);
System.out.println(flag);
} catch (AuthenticationException e) {
System.out.println("用户名或者密码出错");
e.printStackTrace();
}
}
}
—配置文件
#users标签:用于指定用户信息,以及用户的角色
#注意:shiro的支持一个用于有多个角色的
#用户名= 密码, 角色1, 角色2, …, 角色N
#如果出现多个角色,用于取的是角色的并集
[users]
admin = 123456,roleAdmin,roleEdu
#roles标签:用于设置角色的信息,包括的角色以及权限
#权限字符串的格式为:模块名:操作:操作... 类似我们的子权限的设置
[roles]
roleAdmin = user:query,user:add,user:delete,modular:*
roleEdu = edu:query,edu:add
3.5.2.第二步:在Realm授权
后期可以用spring mvc中的接口注入
package cn.gzsxt.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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import cn.gzsxt.javabean.ActiveUser;
import cn.gzsxt.javabean.Role;
/**
* Realm :所有Realm的父接口
* AuthenticatingRealm :只有权限校验,没有权限授权的Realm
* AuthorizingRealm : 既有权限校验,权限授权的Realm (主要就是使用它)
* JdbcRealm:使用实现的对数据库 操作的代码的Realm。它已经写死了表名,必要要按它定义的表名定义数据库表。(灵活性差)
*
* 注意事项:使用Realm,将验证认证信息与获得授权信息,都放在一起!!!!!
* @author ranger
*
*
*/
public class MyRealm extends AuthorizingRealm {
/**
* 用于权限校验
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
try {
System.out.println("=权限校验=");
//第一步:获得请求过来的用户名
Object principal = token.getPrincipal();
//第二步:根据用户查询数据库,返回用户信息 (模拟数据,查询到了"admin")
if("admin".equals(principal)){
//第三步:如果用户名相同,校验密码,如果密码正确,不会报异常,如果校验不通过就报异常
//参数1:用户名
//参数2:校验的密码,从数据库查询出来的
//参数3:realm的名字 。随便设置一个唯一字符串
ActiveUser user=new ActiveUser();
user.setUserId(1);
user.setAge(20);
user.setPassword("8763d15228c560fed665e1fe73b2f601");
user.setUsername("admin");
user.setStatus(0);
//4). md5加密盐值
ByteSource credentialsSalt = ByteSource.Util.bytes("nchu234we");
//参数1:用于设置认证信息,返回给调用对象的
//参数2:校验的密码
//参数3:如果配置了Md5加密,而且设置了需要加盐,该参数就是密码的盐
//参数4:指定realm的名字,随便写个唯一的字符串就可以。建议直接使用this.getName();
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user,"8763d15228c560fed665e1fe73b2f601",credentialsSalt,this.getName());
return authenticationInfo;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 权限授权,必须在通过验证后才执行的方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
System.out.println("===权限授予====");
//获得当前通过验证的用户,为什么可以获得校验后的用户信息呢?
//答:因为就是如果通不过校验,肯定就会有有授权,进入了授权肯定就有校验的认证用户了
ActiveUser user=(ActiveUser) token.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.addStringPermission("user:query");
authorizationInfo.addStringPermission("user:add");
authorizationInfo.addStringPermission("user:delete");
authorizationInfo.addStringPermission("user:edit");
authorizationInfo.addStringPermission("modular:*");
authorizationInfo.addRole("roleAdmin");
//将权限设置就是里面
role.setPermisssion(authorizationInfo.getStringPermissions());
return authorizationInfo;
}
}
4.总结
1.Shiro是什么
一个权限控制框架。
2.Shiro的作用是什么
就是在实现Rbac系统的时候,使用它来做权限验证,可以减少我们的开发的代码。
3.我们将使用的API记住
IniSecurityManagerFactory : 用于加载配置文件,创建SecurityManager对象
SecurityManager :就是整个Shiro的控制对象
SecurityUtils :SecurityManager 工具类,用于获得Subject对象
Subject :身份类
UsernamePasswordToken 身份信息构建类 (Token 令牌)
AuthorizingRealm 支持校验与授权的Realm
AuthenticationInfo 校验成功返回的信息的父类
SimpleAuthenticationInfo 校验成功返回信息类
Md5Hash Md5加密类
ByteSource 用于构造Md5加盐的类。
HashedCredentialsMatcher Md5算法校验器,用于支持Md5校验
AuthorizationInfo 授权成功返回的信息类的父类
PrincipalCollection 授予是获得验证信息的类
SimpleAuthorizationInfo 授权成功返回的信息类的实现类