spring-boot工具类(一)
一·导入相关依赖
相关依赖导入
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();
}
}