zl程序教程

您现在的位置是:首页 >  后端

当前栏目

SpringBoot 与Shiro 整合系列(二)实现MD5盐值加密

SpringBoot加密 实现 系列 整合 MD5 shiro
2023-09-11 14:20:19 时间

SpringBoot 与Shiro 整合- 密码的比对、MD5盐值加密


密码的比对、MD5盐值加密都是通过 CrendentialsMatcher(凭证匹配器)来实现的。

一、Shiro认证时的密码比对

在Shiro进行密码比对时,一定会去拿UsernamePasswordToken 和SimpleAuthenticationInfo中封装的密码信息,那么此时要调用UsernamePasswordToken的 getPassword方法,或者调用SimpleAuthenticationInfo的getCredentials方法。

1.在UserNamePasswordkToken 中的 getPassword() 方法中打上断点,往前跟踪一下即可。
在这里插入图片描述
2.开启debug模式,点击登录,跟踪发现
SimpleCredentialsMatcher类有一个doCredentialsMatch方法,在该方法中
就进行了密码比对工作:
在这里插入图片描述

    /**
     * This implementation acquires the {@code token}'s credentials
     * (via {@link #getCredentials(AuthenticationToken) getCredentials(token)})
     * and then the {@code account}'s credentials
     * (via {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) getCredentials(account)}) and then passes both of
     * them to the {@link #equals(Object,Object) equals(tokenCredentials, accountCredentials)} method for equality
     * comparison.
     *
     * @param token the {@code AuthenticationToken} submitted during the authentication attempt.
     * @param info  the {@code AuthenticationInfo} stored in the system matching the token principal.
     * @return {@code true} if the provided token credentials are equal to the stored account credentials,
     *         {@code false} otherwise
     */
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenCredentials = getCredentials(token);
        Object accountCredentials = getCredentials(info);
        return equals(tokenCredentials, accountCredentials);
    }

3.继续跟踪,发现我们自定义Realm的父类方法调用了 CrendebtialsMatcher组件 进行密码比对

    /**
     * Asserts that the submitted {@code AuthenticationToken}'s credentials match the stored account
     * {@code AuthenticationInfo}'s credentials, and if not, throws an {@link AuthenticationException}.
     *
     * @param token the submitted authentication token
     * @param info  the AuthenticationInfo corresponding to the given {@code token}
     * @throws AuthenticationException if the token's credentials do not match the stored account credentials.
     */
    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
            if (!cm.doCredentialsMatch(token, info)) {
                //not successful - throw an exception to indicate this:
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                    "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                    "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
    }

总结一下也就是,密码的比对是通过AuthenticatingRealm 的 CredentialsMatcher属性的doCredentialsMatch方法来完成的。

二、加密

1.密码的加密

在数据表中存的密码不应该是123456,而应该是123456加密之后的字符串,而且还要求这个加密算法是不可逆的即由加密后的字符串不能反推回来原来的密码,如果能反推回来那这个加密是没有意义的

著名的加密算法,比如 MD5,SHA1

2.MD5加密

1). 如何把一个字符串加密为MD5
2). 使用MD5加密算法后,前台用户输入的字符串如何使用MD5加密,需要做的是:替换当前Realm的credentialsMatcher属性。直接使用HashedCredentialsMatcher 对象,并设置加密算法即可。

如图所示:Md5CredentialsMatcher已经过期了,提示使用HashedCredentialsMatcher
在这里插入图片描述

下面为 HashedCredentialsMatcher 哈希凭据匹配器 的继承关系:
在这里插入图片描述

3.实现MD5加密

步骤如下:

3.1在系列(一)的基础上,修改ShiroConfig配置类:

(1)添加 凭证匹配器 配置方法

	/**
	 * 配置 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了,
	 * 所以我们需要修改下doGetAuthenticationInfo中的代码;)
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher(){
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		//设置加密算法
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");
		//设置加密次数,比如两次,相当于md5(md5())
		hashedCredentialsMatcher.setHashIterations(1024);
		return hashedCredentialsMatcher;
	}

(2)修改创建Realm方法:
在方法设置 凭证匹配器

	/**
	 * 1.创建Realm
	 */
	@Bean
	public UserRealm userRealm(){
		UserRealm userRealm = new UserRealm();
		//设置 凭证匹配器
		userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return userRealm;
	}

3.2 修改下自定义Realm类中doGetAuthenticationInfo方法中的代码:

返回的SimpeAuthenticationInfo对象时,需要采用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName)构造函数来构建对象。

	/**
	 * 执行认证逻辑
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("执行认证逻辑");
		
		//编写shiro 判断逻辑,判断用户名和密码
		//1.判断用户名
		UsernamePasswordToken userToken=(UsernamePasswordToken) token;
		//根据用户名获取用户信息
		User user=userService.findUserByName(userToken.getUsername());
				
		if(user == null){
			//用户不存在
			return null;//shiro底层会抛出 UNknowAccountException
		}
		//2.判断密码     第一个参数:需要返回给 subject.login方法的数据   第二个参数:数据库密码       第三个参数:realm的名字
//		return new SimpleAuthenticationInfo(user, user.getPassword(), "");
		
		//根据用户的情况,来构建AuthenticationInfo对像并返回,通常使用的实现类是SimpleAuthenticationInfo
		//以下信息是从数据库获取的
		//1).principal:认证的实体类信息。可以是username,也可以是数据表对应的用户的体类对象
		Object principal=user;
		//2).credentials:密码(数据库获取的用户的密码)
		Object credentials=user.getPassword();
		//3).realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName=getName();
		ByteSource credentialsSalt =null;
//		//4).盐值
//		ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());//盐值 要唯一
		return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
	}

3.3 修改数据库中的用户表的登录密码

查看源码,看一下shiro是怎么加密的:通过断点可以看到,实际的加密为
在这里插入图片描述
创建main,得到 密码明文111111的MD5加密1024次后的密文:879d6457d5315e047d842e5507c262b5

	public static void main(String[] args) {
		String hashAlgorithmName="MD5";//加密算法(与配置文件中的一致)
		String credentials="111111";//密码
//		ByteSource salt=ByteSource.Util.bytes("admin");//盐值
		ByteSource salt=null;//盐值
		int hashIterations=1024;//加密次数(与配置文件中的一致)
		System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
	}

修改到数据库中:
在这里插入图片描述

3.4 测试

打断点,debug方法启动项目,输入用户名 admin 密码 111111,点击登录,密码匹配如下:
在这里插入图片描述
在这里插入图片描述
发现密码相等,登录成功,进入test.html页面:
在这里插入图片描述

三、MD5盐值加密

1.为什么使用MD5盐值加密?
shiro用密码匹配,密码一样,就ok。如果两个用户的密码一样,则就会造成麻烦。所以使用MD5盐值加密。
2.盐值加密简单来说就是:两个一样的西红柿,加不同的盐炒出来的味道不一样。
3.什么适合作为盐值呢?肯定是惟一的东西。比如用户名(一般采用手机号,或者邮箱等等)(用户id也ok)
4.盐值获得方法:

在这里插入图片描述

MD5盐值加密实现:

(1)修改下自定义Realm类中doGetAuthenticationInfo方法中的代码
在这里插入图片描述
(2)修改数据库中的用户表的登录密码:
在这里插入图片描述
在这里插入图片描述
(3)测试
打断点,debug方法启动项目,输入用户名 admin 密码 111111,点击登录,密码匹配如下:
在这里插入图片描述
在这里插入图片描述
测试成功!!!

四、总结

1.密码的比对是 通过 AuthenticatingRealm 的 CrendentialsMatcher 属性来进行的

2.盐值最好设置为一个用户的唯一值(一般采用用户名)

3.盐值加密的注意点

  1. 在自定义Realm的认证方法(doAuthenticationInfo方法),返回的SimpeAuthenticationInfo对象时,需要采用SimpleAuthenticationInfo(principal,credentials, credentialsSalt, realmName)构造函数来构建对象
  2. 计算盐值:ByteSource对象,可以采用ByteSource.Util.byte(String UserID)获取
  3. 盐值需要唯一: 一般使用随机字符串或 user id
  4. 如何计算盐值加密后的密码的值:使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);

4.CrendentialsMatcher组件(凭证匹配器)作用
(1)用来进行密码比对
(2)用来对密码进行加密

五、完整代码

点击此处下载

上一篇:SpringBoot 与Shiro 整合系列(一)用户认证与授权实战