Shiro入门案例
一、需求
- 使用纯Maven项目完成Shiro入门案例,注意不是使用SpringBoot项目
- 入门案例需求为:使用Shiro进行认证、授权、加密以及使用自定义的realm来完成认证过程的功能
- 不涉及数据库,使用Shiro规定ini文件进行用户信息、角色、访问权限的配置
二、创建项目
创建一个Maven的jar类型项目
具体的代码实现如下:
三、添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bjsxt</groupId>
<artifactId>shiro_demo1</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 引入依赖-->
<dependencies>
<!--Shiro的核心依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<!--日志依赖-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
四、Shiro认证
1、编写配置文件
在resources目录下编写Shiro的全局配置文件——shiro.ini
# 定义用户名、密码以及用户可以具有的角色
[users]
zhangsan=123
lisi=456
2、编写测试类
public class ShiroTest {
/**
* 认证功能测试
*/
@Test
public void testLogin() {
// 1.创建一个SecurityManager容器工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.从工厂中获取SecurityManager容器对象实例
SecurityManager securityManager = securityManagerFactory.getInstance();
// 3.将SecurityManager容器设置进SecurityUtils中
SecurityUtils.setSecurityManager(securityManager);
// 4.从SecurityUtils中获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 5.创建一个认证器令牌,这个令牌中含有登录信息
AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "123");
// 6.调用subject的login方法进行认证
try {
subject.login(token);
System.out.println("登录成功"); // 未抛出异常程序会正常执行
} catch (UnknownAccountException e) { // 表示用户名不存在抛出异常
System.out.println("账户不存在!!");
} catch (IncorrectCredentialsException e) { // 表示凭证错误异常也就是密码错误
System.out.println("密码错误!!");
} catch (AuthenticationException e) {
System.out.println("登录失败!");
}
}
}
3、结果
4、小结
未使用Shiro框架之前我们登录的功能是通过自己去获取用户登录信息(用户名和密码),然后从数据库中查询对应的用户信息是否存在,如果存在就说明登录成功,对应不上就登录失败
使用了Shiro框架后,从数据库查询的事情就不需要我们来做了,而是交给了SecurityManager容器去做,我们需要将登录的信息封装到一个Subject类中,具体的就是封装到AuthenticationToken认证令牌器中,然后由Shiro来完成对数据库查询对比然后给出结果
如果登录成功,不会有提示,登录失败后会直接抛出异常AuthenticationException,表示认证失败,所以我们可以通过对登录用的代码进行异常的捕获,如果捕获到异常就说明登录失败,如果为捕获到异常则说明登录成功,程序正常运行
5、执行流程图如下
五、Shiro授权
这里使用的还是上面的案例:
1、判断角色
修改配置文件
给用户zhangsan添加两个角色role1,role2
[users]
zhangsan=123,role1,role2
lisi=456
编写测试方法
/**
* 判断角色功能测试
*/
@Test
public void testRole() {
// 1.创建一个SecurityManager容器工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.创建SecurityManager对象实例
SecurityManager securityManager = securityManagerFactory.getInstance();
// 3.将SecurityManager容器设置进SecurityUtils中
SecurityUtils.setSecurityManager(securityManager);
// 4.从SecurityUtils中获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 5.创建一个认证器令牌,这个令牌中含有登录信息
AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123");
// 6.调用subject的login方法进行认证
// 使用Shiro框架时,如果登录成功,不会有提示,登录失败后会直接抛出异常AuthenticationException,表示认证失败,所以我们可以通过
// 对登录用的代码进行异常的捕获,如果捕获到异常就说明登录失败,如果为捕获到异常则说明登录成功,程序正常运行
try {
subject.login(token);
System.out.println("登录成功"); // 未抛出异常程序会正常执行
// 判断用户是否具备某个角色一定是这个用户已经认证成功了,否则不必判断
boolean b = subject.hasRole("role1");
System.out.println(subject.getPrincipal() + "用户是否具备role1角色:" + (b ? "是" : "否"));
} catch (UnknownAccountException e) { // 表示用户名不存在抛出异常
System.out.println("账户不存在!!");
} catch (IncorrectCredentialsException e) { // 表示凭证错误异常也就是密码错误
System.out.println("密码错误!!");
} catch (AuthenticationException e) {
System.out.println("登录失败!");
}
}
结果
2、判断权限
修改配置文件
[users]
zhangsan=123,role1,role2
lisi=456
注释:给对应的角色添加相应的权限
[roles]
role1=user:insert,user:update
编写测试方法
/**
* 判断权限功能测试
*/
@Test
public void testPermit() {
// 1.创建一个SecurityManager容器工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.创建SecurityManager对象实例
SecurityManager securityManager = securityManagerFactory.getInstance();
// 3.将SecurityManager容器设置进SecurityUtils中
SecurityUtils.setSecurityManager(securityManager);
// 4.从SecurityUtils中获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 5.创建一个认证器令牌,这个令牌中含有登录信息
AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123");
// 6.调用subject的login方法进行认证
// 使用Shiro框架时,如果登录成功,不会有提示,登录失败后会直接抛出异常AuthenticationException,表示认证失败,所以我们可以通过
// 对登录用的代码进行异常的捕获,如果捕获到异常就说明登录失败,如果为捕获到异常则说明登录成功,程序正常运行
try {
subject.login(token);
System.out.println("登录成功"); // 未抛出异常程序会正常执行
// 判断用户是否具备哪个权限一定是这个用户已经认证成功了,否则不必判断
boolean b = subject.isPermitted("user:insert");
System.out.println(subject.getPrincipal() + "用户是否具备user:insert权限:" + (b ? "是" : "否"));
} catch (UnknownAccountException e) { // 表示用户名不存在抛出异常
System.out.println("账户不存在!!");
} catch (IncorrectCredentialsException e) { // 表示凭证错误异常也就是密码错误
System.out.println("密码错误!!");
} catch (AuthenticationException e) {
System.out.println("登录失败!");
}
}
3、小结
授权:表示的就是判断某个认证(登录)成功的用户是否具备某个权限或角色
4、执行流程如下
六、Shiro加密
1、概述
在实际开发中,为了防止用户信息泄露,我们一般会对一些敏感信息如:用户密码等进行加密后存储。Shiro框架中内嵌了很多加密算法,如:Base64、MD5等,使用Shiro框架可以方便的实现加密功能
2、编写测试方法
假设数据库中存在一个用户的密码为123,那么Shiro要对其进行加密后保存,那么就可以有一下几种情况:
加密一次
// 进行一次MD5加密
Md5Hash firstHash = new Md5Hash(password);
System.out.println("经过MD5加密后的密码为:" + firstHash.toHex());
结果
加盐加密一次
// 如果用户输入的密码过于简单,那么一次加密后的结果其实并没有那么安全,所以在我们可以再进行一些处理
// 进行一次加盐加密
Md5Hash firstHashWithSalt = new Md5Hash(password, "abc"); // 第二个参数就是所谓的盐,采用这种方式时MD5会将用户输入的密码与盐一起进行加密来保证不被破解
System.out.println("经过MD5加盐加密后的密码为:" + firstHashWithSalt.toHex());
结果
加盐加密两次
// 如果还不放心我们还可以对加盐后的密码进行二次加密
Md5Hash secondHashWithSalt = new Md5Hash(password, "abc", 2); // 第三个参数就是指定要加密几次
System.out.println("经过MD5加盐并二次加密后的密码为:" + secondHashWithSalt.toHex());
结果
3、小结
加密的过程一般在用户注册成功后就将密码进行加密然后保存到数据库中,也就是说,用户在认证登录的时候数据库中的密码已经是加密后的密码了。
七、自定义Realm
这就出现了一个问题,Shiro的认证功能并不知道用户输入的密码应该通过几次加密或者加盐才能与数据库中的密码匹配上,他默认只会比较用户输入的密码和ini文件或数据库中保存的密码,那么我们应该怎么样才能认证密码呢?
我们需要自定义一个realm并使用凭证匹配器来告知Shiro当用户输入密码后应该怎么样加密操作才能与数据库或ini文件中保存的密码保持一致
1、需求
-
用户注册时输入的密码要经过MD5,甚至加盐加密的过程
-
将加密后的密码存储到数据库中
-
shiro的认证功能在校验用户信息时,默认只会直接的比较用户输入的密码和数据库中的密码,这样就不对了,所以我们需要进行干预,也就是要自定义realm和使用凭证匹配器,不让它走之前的默认逻辑了。
2、创建自定义的Realm类
// 自定义Realm类要继承AuthenticatingRealm类,重写方法
public class CustomRealm extends AuthenticatingRealm {
/**
* 重写该方法,之前认证时调用的 subject.login(token); 方法的时候底层实际上就是调用了这个方法
* 重写此方法后,我们再调用 subject.login(token); 方法就会执行我们重写后的逻辑代码进行认证
* @param authenticationToken 存放的就是用户输入的用户名和密码
* @return 认证信息
* @throws AuthenticationException 认证失败异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行自定义的realm类的认证方法进行认证!!!");
// 1.获取用户输入的用户名
String username = authenticationToken.getPrincipal().toString();
// 2.获取用户输入的密码,该方法返回值是一个char类型数组,我们在这里将他转化为String类型字符串
String password = new String((char[]) authenticationToken.getCredentials());
// 3.根据用户名从数据库中查询用户信息
String usernameFromDB = "zhangsan";
String passwordFromDB = "b106dc6352e5ec1f8aafd8c406d34d92"; // 这里模拟数据库中查询的密码
// 4.判断用户是否存在
if(username.equals(usernameFromDB)) { // 用户名存在
return new SimpleAuthenticationInfo(
usernameFromDB, // 凭证,也就是标识用户身份的用户名
passwordFromDB, // 从数据库中查询的密码!!
ByteSource.Util.bytes("abc"), // 加密时的盐
"zhangsan"); // 这个参数可以随便写,但是一般写成用户名
}
// 返回空说明用户名不存在
return null;
}
}
3、修改配置文件
[main]
// 添加凭证匹配器
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
// 告知凭证匹配器密码加密次数
md5CredentialsMatcher.hashIterations=2
// 给自定义的realm类起别名
customRealm=com.bjsxt.realm.CustomRealm
// 给自定义realm类添加凭证匹配器
customRealm.credentialsMatcher=$md5CredentialsMatcher
// 将自定义类交给sevurtiryManager容器管理
securityManager.realms=$customRealm
[users]
zhangsan=b106dc6352e5ec1f8aafd8c406d34d92,role1,role2
lisi=456
[roles]
role1=user:insert,user:add
4、结果
重新执行刚开始的认证方法来验证自定义类是否被使用,结果如下:
八、总结
本次案例并未设计数据库,所以代码量也不少,显得使用Shiro框架来实现权限管理并没有那么好用,这是因为我们将信息都配置或存储在了ini文件中,后面放入数据库并通过SpringBoot整合Shiro之后,就会让Shiro显得更加强大一些了,总的来说:
Shiro的作用小小总结:
- 认证功能,自动数据库查询,并与用户输入信息进行对比返回结果
- 授权功能,判断登录成功用户的角色、权限并对其进行过滤筛选
- 加密功能:对用户信息可以自动加密,保证用户信息的安全
- Session管理:在Web项目中对Session也能够很好的管理