编程语言
首页 > 编程语言> > Marco's Java【Shiro入门(四) 之 Shiro的散列算法及凭证配置】

Marco's Java【Shiro入门(四) 之 Shiro的散列算法及凭证配置】

作者:互联网

前言

到目前为止,Shrio的基本用法大家应该掌握的差不多了,但是完全没有涉及到任何加密?如何才能保证我的账户安全?确实,缺少加密的Shiro是没有灵魂滴,所以本节我们就要着重的讲到,如果使用Shiro的加密,提升"蓝宝石骑士Shiro" 的护盾等级,给己方队友提供安全的输出环境。提升 “护盾” 等级本质就是算法的加密,最为常用的就是散列算法啦。

什么是散列算法

通过散列算法的字面的意思,就可以大致猜的出来,这是一种散开后再排列的算法或者说是某种规则,说白了在Shiro中散列算法就是对用于字符串的加密。
说到散列,我们其实之前肯定遇到过,而且还很熟悉。因为我们的"老朋友" hashcode 就是通过散列算法(Hash算法)得出来的结果。本节博文不会去深究这个算法的根源和原理,后续有机会会专门再针对于散列算法(Hash算法)写一个系列博文啦~ 不过不妨碍我们先大概了解一下

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

哈希表是根据设定的哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址。作为线性数据结构与表格和队列等相比,哈希表无疑是查找速度比较快的一种。

通过将单向数学函数(有时称为“哈希算法”)应用到任意数量的数据所得到的固定大小的结果。如果输入数据中有变化,则哈希也会发生变化。哈希可用于许多操作,包括身份验证和数字签名。也称为“消息摘要”。

简单解释:哈希(Hash)算法,即散列函数。它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。哈希函数的这种单向特征和输出数据长度固定的特征使得它可以生成消息或者数据。
(以上散列算法定义采纳至Beyond_2016博文常见的hash算法及其原理)

我们通常使用的散列算法有MD5(MD5 Message-Digest Algorithm消息摘要算法) 和SHA1(Secure Hash Algorithm安全哈希算法),它们也是在消息认证和数字签名中普遍使用到的。这两者最大的区别就是SHA1相对MD5会更加安全,这里涉及到一个概念就是摘要MD(是一种用于检查报文是否正确的方法,例如信道噪音的干扰),SHA-1摘要比MD5摘要长32 位,因此破解起来比MD5更困难,取而代之,在硬件设施相同的情况下,SHA1的运行速度会比MD5更慢,万物就是有两面性,想要安全就必须得付出点代价的,对吧?

报文摘要:指单向哈希函数算法将任意长度的输入报文经计算得出固定位的输出称为报文摘要,所谓单向是指该算法是不可逆的找出具有同一报文摘要的两个不同报文是很困难的。

Shiro中的MD5散列算法

基础的东西总归是枯燥乏味的,从现在开始那我们来点实质性的 “甜点” 来给大家缓解下啦~
根据上面的定义我们了解到,散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一 些md5 解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和ID(即盐),这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
话不多说,先来一个小工具玩玩吧~

package com.marco.utils;

import org.apache.shiro.crypto.hash.Md5Hash;

public class MD5Utils {
	public static void main(String[] args) {
		//原密码
		String source = "123456";
		//加密一次不加盐
		Md5Hash md5HashOnce = new Md5Hash(source);
		System.out.println("加密一次不加盐:" + md5HashOnce);
		//加密一次加盐(这里使用的盐是用户名)
		Md5Hash md5HashOnceWithSalt = new Md5Hash(source, "marco");
		System.out.println("加密一次加盐:" + md5HashOnceWithSalt);
		//加密两次加盐(这里使用的盐是用户名)
		Md5Hash md5HashTwiceWithSalt = new Md5Hash(source, "marco武汉", 2);
		System.out.println("加密两次加盐:" + md5HashTwiceWithSalt);
	}
	
	/**
	 * 设置UserRealm的加密规则
	 * @param source
	 * @param salt
	 * @param hashIterations
	 * @return
	 */
	public static String md5HashTwiceWithSalt(String source, String salt, Integer hashIterations) {
		return new Md5Hash(source, salt, hashIterations).toString();
	}
}

一般来说加盐是为了让我们的加密后的数据更加不可逆,因此,会把一独自有的东西作为盐,比如说用户名,邮箱等等。在后面的小项目中,我们会采用第三种加密两次并加盐的方式来操作。
在这里插入图片描述

使用凭证匹配器完善用户的登录验证

我们来修改一下上一节的代码,添加加密的凭证匹配器,设置加密方式,为了达成加密的效果,我这里修改了之前在UserServiceImpl中的用户密码为加密后的密码
第一步:修改UserServiceImpl

package com.marco.service.impl;

import java.util.ArrayList;
import java.util.List;

import com.marco.domain.User;
import com.marco.service.UserService;
import com.marco.utils.MD5Utils;

public class UserServiceImpl implements UserService {

	@Override
	public User queryUserByUserName(String userName) {
		//模拟数据库查询用户信息
		switch (userName) {
		case "spiderman":
			return new User("spiderman", MD5Utils.md5HashTwiceWithSalt("123456", "spiderman", 2));
		case "ironman":
			return new User("ironman", MD5Utils.md5HashTwiceWithSalt("123456", "ironman", 2));
		case "marco":
			return new User("marco", MD5Utils.md5HashTwiceWithSalt("123456", "marco", 2));
		default:
			return null;
		}
	}

	@Override
	public List<String> queryRolesByUserName(String userName) {
		//模拟给用户授权角色
		List<String> roles = new ArrayList<String>();
		roles.add("systemAdmin");
		roles.add("statisticsAdmin");
		roles.add("customerAdmin");
		return roles;
	}

	@Override
	public List<String> queryPermissionsByUserName(String userName) {
		//模拟给角色分配权限
		List<String> permissions = new ArrayList<String>();
		permissions.add("user:query");
		permissions.add("user:delete");
		permissions.add("user:add");
		return permissions;
	}
}

第二步:设置CredentialsMatcher凭证匹配器
凭证匹配器的英文是CredentialsMatcher,但是CredentialsMatcher是个接口,因此按照以往的经验,我们还是得去找它的得实现类
在这里插入图片描述
哇… 这么多我到底选择哪一个呢?之前我们提到Shiro是通过散列算法进行加密得,那么和散列相关得匹配器就是HashedCredentialsMatcher了,HashedCredentialsMatcher中包含了很多子类,每一个子类代表这一种算法,这里面可以找到两个我们还比较熟悉得算法,Md5和Sha1。
在这里插入图片描述
我们点进去看看HashedCredentialsMatcher得内容,其实这几个参数很好理解,分别是 使用得算法的名称、散列次数、是否加"盐"、密文得存储方式(默认true是密文进行16进制存储,false是按照Base64-encoded进行存储)
在这里插入图片描述
说了这么长时间得凭证匹配器,那它到底应该在哪里被注入进去呢?其实仔细想想,它能够被注入得地方就只有Realm了,原因是我们所有得加密解密工作都是在Realm中,还记得我们得SimpleAuthenticationInfo吗,它本质上就是带着我们输入得密码去找凭证匹配器匹配,因此我们点开看抽象类AuthenticatingRealm就可以找到设置凭证匹配器的方法
在这里插入图片描述
知道了该使用何种Credential、Credential需要做哪些配置以及凭证匹配器在哪里设置之后,我们就可以开始修改此前得代码啦~

package com.marco.test;

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.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

import com.marco.realm.UserRealm;

public class LoginController {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		
		//模拟获取登录的账号密码
		String username = "marco";
		String password = "123456";
		//将账号密码封装到token(令牌)中
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		
		//创建安全管理工厂
		IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory();
		//获取DefaultSecurityManager安全管理对象
		DefaultSecurityManager securityManager = (DefaultSecurityManager) securityManagerFactory.getInstance();
		//设置Realm获取数据(Realm在开发中是从数据库中获取数据)
		UserRealm userRealm = new UserRealm();
		//设置凭证匹配器,并设置加密方式
		HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("MD5");
		//设置加密次数
		credentialsMatcher.setHashIterations(2);
		//向userRealm中加入匹配规则
		userRealm.setCredentialsMatcher(credentialsMatcher);
		//告知securityManager使用的是自定义Realm:userRealm
		securityManager.setRealm(userRealm);
		//把安全管理器绑定到当前运行环境
		SecurityUtils.setSecurityManager(securityManager);
		//从当前环境里面得到Subject主体对象
		Subject subject = SecurityUtils.getSubject();
		//调用主体的登陆方法
		try {
			subject.login(token);
			System.out.println("登陆成功!");
		} catch (IncorrectCredentialsException e) {
			System.err.println("密码不正确");
		}catch (UnknownAccountException e) {
			System.err.println("用户名不存在");
		}
	}
}

第三步:测试
修改到此为止啦,我们接着来测试一下用户登录吧~ 咱们先输入错误的账号
在这里插入图片描述
再输入错误的密码
在这里插入图片描述
接着输入正确的用户名和密码
在这里插入图片描述

后语

到本节为止,咱们的Shiro入门就结束啦,Shiro的进阶的第一个阶段,我们就要使用Shrio+SSM建成Maven项目来一次 “串烧” 啦,有兴趣的朋友,或者想继续提升的朋友可以继续阅读Shiro+SMM集成Maven项目串烧系列篇哦
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(上)】
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(中)】
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(下)】

标签:Marco,Java,算法,哈希,import,加密,散列,Shiro
来源: https://blog.csdn.net/weixin_44698119/article/details/98176126