spring-boot工具类分享(一)使用BCryptPasswordEncoder加密校验密码------公用工具类(密码加密·密码校验·获取登录信息·获取指定范围或者指定位数的随机数字)

一·导入相关依赖

相关依赖导入
springframework.security导入

 	<dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>

二·对于BCryptPasswordEncoder简单介绍


在项目中,我们常常对于密码的加密使用spring
security中提供了一个加密类BCryptPasswordEncoder,它可以用来对密码字符串进行加密,得到加密后的字符串。它采用哈希算法
SHA-256
+随机哈希值+密钥对密码进行加密,安全性较高,对于相同的密码,进行加密之后得到的内容也是不同的,但也因此相比于其他的加密算法,增加了性能的开销。

那我们平时使用的加密算法和哈希算法究竟有什么区别呢
加密算法是一种可逆的算法,基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码为“密文”,但在用相应的密钥进行操作之后就可以得到原来的内容

哈希算法是一种不可逆的算法,是把任意长度的输入通过散列算法变换成固定长度的输出,输出就是散列值,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。

对于哈希算法不了解的同学可以查看我的另一篇博客,里边详细介绍了Hash算法和HashMap的过程,对此有一些图解可以方便大家记忆。
数据结构重点------散列表及java中的Hashmap

在一般的业务流程中,首先,再前端用户填写注册信息提交后,账户信息先到controller层,然后依次经过service层和dao层,最后入库。其中对于密码的加密要放在service层进行,对密码进行加密后再入库。

在spring
security中,BCryptPasswordEncoder接口可以用来对密码进行加密,调用其中的encode方法返回一个加密后的字符串,加密后数据库中存储的是加密过后的字符串。
在虽然可以在使用时直接从spring的配置文件中配置一个加密类的bean,这样在service中可以直接注入,但在实际的项目中,都是封装在一个公共工具类的中,这样可以方便我们调用

在注册过程中的密码加密所调用的方法
在公共工具类中的写法

	/**
	 * 密码加密
	 * 在公共工具类中的写法
	 * @param password
	 *            未加密文本
	 * @return 加密后的文本
	 */
	public static String QuickPassword(String password) {
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		return passwordEncoder.encode(password);
	}

BCryptPasswordEncoder 中encode方法的源码
我们看到方法encode中先基于某种规则得到了一个Hash值(也就是代码中的salt,至于为啥用salt,大概是写源码的叔叔,重口味的同时觉得盐是比较散的吧,毕竟不可数),然后在调用BCrypt.hashpw(),传入明文密码和Hash值。

public String encode(CharSequence rawPassword) {
        String salt;
        if (this.strength > 0) {
            if (this.random != null) {
                salt = BCrypt.gensalt(this.strength, this.random);
            } else {
                salt = BCrypt.gensalt(this.strength);
            }
        } else {
            salt = BCrypt.gensalt();
        }

        return BCrypt.hashpw(rawPassword.toString(), salt);
    }

接下来,让我们康一康用 BCrypt.hashpw()做了什么,这个方法中先根据传入的Hash值,通过一种规则,从salt得到real_salt,后续的操作都是用这个real_salt来进行Hash,最终得到加密字符串。
注意:传入的salt并不是最终用来加密的Hash值,方法中通过salt得到了real_salt,因为后边的登录的匹配方法matches中要用到这一点。hashpw()是在加密和解密的过程中都会用到的方法

public static String hashpw(String password, String salt) throws IllegalArgumentException {
        char minor = 0;
        int off = false;
        StringBuilder rs = new StringBuilder();
        if (salt == null) {
            throw new IllegalArgumentException("salt cannot be null");
        } else {
            int saltLength = salt.length();
            if (saltLength < 28) {
                throw new IllegalArgumentException("Invalid salt");
            } else if (salt.charAt(0) == '$' && salt.charAt(1) == '2') {
                byte off;
                if (salt.charAt(2) == '$') {
                    off = 3;
                } else {
                    minor = salt.charAt(2);
                    if (minor != 'a' || salt.charAt(3) != '$') {
                        throw new IllegalArgumentException("Invalid salt revision");
                    }

                    off = 4;
                }

                if (saltLength - off < 25) {
                    throw new IllegalArgumentException("Invalid salt");
                } else if (salt.charAt(off + 2) > '$') {
                    throw new IllegalArgumentException("Missing salt rounds");
                } else {
                    int rounds = Integer.parseInt(salt.substring(off, off + 2));
                    String real_salt = salt.substring(off + 3, off + 25);

                    byte[] passwordb;
                    try {
                        passwordb = (password + (minor >= 'a' ? "\u0000" : "")).getBytes("UTF-8");
                    } catch (UnsupportedEncodingException var13) {
                        throw new AssertionError("UTF-8 is not supported");
                    }

                    byte[] saltb = decode_base64(real_salt, 16);
                    BCrypt B = new BCrypt();
                    byte[] hashed = B.crypt_raw(passwordb, saltb, rounds);
                    rs.append("$2");
                    if (minor >= 'a') {
                        rs.append(minor);
                    }

                    rs.append("$");
                    if (rounds < 10) {
                        rs.append("0");
                    }

                    rs.append(rounds);
                    rs.append("$");
                    encode_base64(saltb, saltb.length, rs);
                    encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);
                    return rs.toString();
                }
            } else {
                throw new IllegalArgumentException("Invalid salt version");
            }
        }
    }

登录过程的工具类

/**
	 * 校验密码正确错误
	 * 
	 * @param rawPassword
	 *            未加密文本
	 * @param encodedPassword
	 *            加密文本
	 * @return true:密码正确 ,false密码错误
	 */
	public static Boolean checkPassword(String rawPassword, String encodedPassword) {
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		return passwordEncoder.matches(rawPassword, encodedPassword);
	}

调用 BCryptPasswordEncoder 中 matches方法
matches方法用来判断一个明文是否和一个加密字符串对应。

这个方法中先对密文字符串进行了一些校验,如果不符合规则直接返回不匹配,然后调用校验方法BCrypt.checkpw,第一个参数是明文,第二个参数是加密后的字符串。

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (encodedPassword != null && encodedPassword.length() != 0) {
            if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
                this.logger.warn("Encoded password does not look like BCrypt");
                return false;
            } else {
                return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
            }
        } else {
            this.logger.warn("Empty encoded password");
            return false;
        }
    }

然后调用checkpw方法,plaintext明文,hashed密文,然后将明文和密文都传入到,hashpw方法中(plaintext, hashed),最后用equalsNoEarlyReturn比较是否相等

   public static boolean checkpw(String plaintext, String hashed) {
        return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
    }

    static boolean equalsNoEarlyReturn(String a, String b) {
        char[] caa = a.toCharArray();
        char[] cab = b.toCharArray();
        if (caa.length != cab.length) {
            return false;
        } else {
            byte ret = 0;

            for(int i = 0; i < caa.length; ++i) {
                ret = (byte)(ret | caa[i] ^ cab[i]);
            }

            return ret == 0;
        }
    }

三·密码公用工具类分享

package com.mbyte.easy.util;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.security.Principal;
import java.util.Random;

/**
 * 
 * @Title: Utility
 * @Description:公用工具类
 * @Author:zln
 * @Since:2019年72月20日
 * @Version:1.1.0
 */
public class Utility {

	/**
	 * 密码加密
	 * 
	 * @param password
	 *            未加密文本
	 * @return 加密后的文本
	 */
	public static String QuickPassword(String password) {
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		return passwordEncoder.encode(password);
	}

	/**
	 * 校验密码正确错误
	 * 
	 * @param rawPassword
	 *            未加密文本
	 * @param encodedPassword
	 *            加密文本
	 * @return true:密码正确 ,false密码错误
	 */
	public static Boolean checkPassword(String rawPassword, String encodedPassword) {
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		return passwordEncoder.matches(rawPassword, encodedPassword);
	}

	/**
	 * 
	 * security 查询登陆用户
	 * 
	 * @return 登录者用户名
	 */
	public static String getCurrentUsername() {
		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		if (principal instanceof UserDetails) {
			return ((UserDetails) principal).getUsername();
		}
		if (principal instanceof Principal) {
			return ((Principal) principal).getName();
		}
		return String.valueOf(principal);
	}

	/**
	 * 
	 * security 查询登陆用户
	 * 
	 * @return 登录者用户
	 */
	public static UserDetails getCurrentUser() {
		UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		return userDetails;
	}

	public static String CHAR_STR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	/**
	 * @Title: getRandomStrByNum
	 * @Description:  获取不同位数的随机字符串
	 * @Author:zln
	 * @param: factor
	 * @Date: 2019-03-13 15:25
	 * @return: java.lang.String
	 * @throws:
	 */
	public static String getRandomStrByNum(int factor) {
		StringBuilder sb = new StringBuilder();
		Random random = new Random();
		for (int i = 0; i < factor; i++) {
			sb.append(CHAR_STR.charAt(random.nextInt(36)));
		}
		return sb.toString();
	}
	/**
	 * @Title: getRandomByNum
	 * @Description: 获取指定位数的随机数字
	 * @Author: zln
	 * @param: factor 位数
	 * @Date: 2019-03-18 14:38 
	 * @return: int
	 * @throws: 
	 */
	public static int getRandomByNum(int factor){
		int min=1,max = 1;
		for (int i = 0; i < factor; i++) {
			if(i < factor-1 ){
				min *= 10;
			}
			max *= 10;
		}
		return getRandomIntInRange(min,max-1);
	}
	/**
	 * @Title: getRandomIntInRange
	 * @Description:  获取指定范围的随机数
	 * @Author: zln
	 * @param: min 最小值
	 * @param: max 最大值
	 * @Date: 2019-03-18 14:38
	 * @return: int
	 * @throws:
	 */
	public static int getRandomIntInRange(int min, int max) {
		return new Random().ints(min, (max + 1)).limit(1).findFirst().getAsInt();
	}

}

猜你喜欢

转载自blog.csdn.net/nn1656353506/article/details/108000781