參考文章:https://blog.csdn.net/weixin_42531204/article/details/105254213
一、使用BcryptPasswordEncoder
spring security中的BCryptPasswordEncoder方法采用SHA-256 +隨機鹽+密鑰對密碼進行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(這個與編碼/解碼一樣),但是采用Hash處理,其過程是不可逆的。
1、加密(encode)
if (user.getPassword() != null) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); user.setPassword(encoder.encode(user.getPassword().trim())); }
encode方法的源碼
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); }
注意:每次加密后的密碼都不一樣,每次的隨機鹽都保存在加密后的密碼中。在比較的時候,隨機鹽重新被取出。即加密后的密碼中,前部分已經包含了鹽信息。
2、密碼匹配(matches)
Result rs = new Result(); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if (!encoder.matches(user.getPassword(), curUser.getPassword())) { rs.setSuccess(false); rs.setMsg("舊密碼錯誤!"); rs.setCode(ResultCode.SUCCESS); return rs; }
用戶登錄時,密碼匹配階段並沒有進行密碼解密(因為密碼經過Hash處理,是不可逆的),而是使用相同的算法把用戶輸入的密碼進行hash處理,得到密碼的hash值,然后將其與從數據庫中查詢到的密碼hash值進行比較。如果兩者相同,說明用戶輸入的密碼正確。
這正是為什么處理密碼時要用hash算法,而不用加密算法。因為這樣處理即使數據庫泄漏,黑客也很難破解密碼(破解密碼只能用彩虹表)。
3、在spring中使用BcryptPasswordEncoder
1)、引入依賴
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> <version>3.1.0.RELEASE</version> </dependency>
2)、使用
@RunWith(SpringRunner.class) @SpringBootTest(classes = MySpringBootApplication.class) public class EncryptTest { @Test public void test() { String pass = "123456"; BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder(); String hashPass = bcryptPasswordEncoder.encode(pass); System.out.println(hashPass); boolean f = bcryptPasswordEncoder.matches("123456",hashPass); System.out.println(f); } }
結果:
$2a$10$h8.leCDNRc37ESzTtCXz/usAotyF2oyGOVP5qjcBgzckk4DAf3hfW true
再次執行,結果如下:
$2a$10$UCNZDxvYpN5RuGWRFaPopOlnoItwimY/o/96ePnIR01UVs2o8bPCa true
可見,每次加密后的結果不一樣,但是每次都能匹配成功。
4、在springboot中使用BcryptPasswordEncoder
如果只是想使用SpringSecurity + SpringBoot完成密碼加密/解密操作,而不使用SpringSecurty提供的其它權證驗證功能。具體步驟如下:
1)、引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2)、添加配置類
我們在添加了spring security依賴后,所有的地址都被spring security所控制了,我們目前只是需要用到BCrypt密碼加密的部分,所以我們要添加一個配置類,配置為所有地址都可以匿名訪問
/** * 安全配置類 */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/**").permitAll() .anyRequest().authenticated() .and().csrf().disable(); } }
3)、在啟動類中配置Bean
@MapperScan("com.zwh.dao") @SpringBootApplication public class MySpringBootApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(this.getClass()); } @Bean public BCryptPasswordEncoder bcryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } }
4)、使用BCryptPasswordEncoder中的方法完成加密/解密
@RunWith(SpringRunner.class) @SpringBootTest(classes = MySpringBootApplication.class) public class EncryptTest { @Test public void test() { String pass = "123456"; BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder(); String hashPass = bcryptPasswordEncoder.encode(pass); System.out.println(hashPass); boolean f = bcryptPasswordEncoder.matches("123456",hashPass); System.out.println(f); } }
結果如下:
$2a$10$S/XfI2AfoDROIZcspSFF7umk/tQsMYVX95PyIzxLrGlrozXuRQ7Ka true
二、MD5加密
1、第一個工具類
1)、MD5加密工具類
不加鹽MD5加密工具類。
實際開發中,采用不加鹽工具類進行兩次加密存入數據庫,再對登錄時獲取的一次加密的密碼再次加密后,與數據庫兩次加密的進行比較,相等的話則表示密碼相同。
public class MD5Tools { /** * 描述:MD5加密方法 * @param text: 待加密的文本 * @return mdrStr: MD5加密后的文本 * @throws NoSuchAlgorithmException * **/ public static String EncoderByMd5(String text) throws NoSuchAlgorithmException{ try { MessageDigest md5 = MessageDigest.getInstance("MD5"); BASE64Encoder base64en = new BASE64Encoder(); return base64en.encode(md5.digest(text.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * MD5加密 * @param str * @return */ public static String getMD5(String str) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("MD5"); // 采用MD5算法 messageDigest.reset(); // 通過restet初始化 messageDigest.update(str.getBytes("UTF-8")); // 加密 } catch (NoSuchAlgorithmException e) { System.out.println("NoSuchAlgorithmException caught!"); System.exit(-1); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } byte[] byteArray = messageDigest.digest(); // 獲取摘要文件 StringBuffer md5StrBuff = new StringBuffer(); // 存放加密后的字符串 for (int i = 0; i < byteArray.length; i++) { if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) md5StrBuff.append("0").append( Integer.toHexString(0xFF & byteArray[i])); else md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); } return md5StrBuff.toString(); } public static String getMD5(String str,String charset) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("MD5"); messageDigest.reset(); messageDigest.update(str.getBytes(charset)); } catch (NoSuchAlgorithmException e) { System.out.println("NoSuchAlgorithmException caught!"); System.exit(-1); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } byte[] byteArray = messageDigest.digest(); StringBuffer md5StrBuff = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) md5StrBuff.append("0").append( Integer.toHexString(0xFF & byteArray[i])); else md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); } return md5StrBuff.toString(); } }
2)、使用
// 驗證用戶名密碼 QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.eq("user_phone", username).or().eq("user_no", username); wrapper.eq("user_pwd", MD5Tools.EncoderByMd5(password)); User user = userService.getOne(wrapper);
2、第二個工具類
加隨機鹽MD5工具類
public class MD5Utils { public static Random random = new SecureRandom(); /** * 加鹽MD5算法 * @param password * @return */ public static String getSaltMD5(String password) throws Exception{ StringBuilder sb = new StringBuilder(16); sb.append(random.nextInt(99999999)).append(random.nextInt(99999999)); int len = sb.length(); if(len < 18){ int diffLen = 16 - len; for(int i = 0; i < diffLen; i ++){ sb.append(0); } } String salt = sb.toString(); password = md5Hex(password + salt); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = password.charAt(i / 3 * 2); char c = salt.charAt(i / 3); cs[i + 1] = c; cs[i + 2] = password.charAt(i / 3 * 2 + 1); } return String.valueOf(cs); } /** * 使用Apache的Hex類實現Hex(16進制字符串和)和字節數組的互轉 * @param str 原明文字符串 * @return 轉換后的字符 */ private static String md5Hex(String str) throws Exception{ MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(str.getBytes()); return new String(new Hex().encode(digest)); } /** * 驗證加鹽后是否和原文一致 * @param password 原始密碼 * @param md5str md5后值 * @return */ public static boolean getSaltverifyMD5(String password, String md5str) throws Exception{ char[] cs1 = new char[32]; char[] cs2 = new char[16]; for (int i = 0; i < 48; i += 3) { cs1[i / 3 * 2] = md5str.charAt(i); cs1[i / 3 * 2 + 1] = md5str.charAt(i + 2); cs2[i / 3] = md5str.charAt(i + 1); } String salt = new String(cs2); return md5Hex(password + salt).equals(String.valueOf(cs1)); } public static void main(String[] args) throws Exception { System.out.println(getSaltverifyMD5("111111","52da3708888616899226ff6f51c85992b39bd2532639cb79")); } }
1)、加密
jkUser.setPassword(MD5Utils.getSaltMD5(randomPassword));
2)、驗證加鹽后是否和原文是否一致
if (!CollectionUtils.isEmpty(users)) { User user = users.get(0); String password = user.getPassword(); boolean saltverifyMD5 = MD5Utils.getSaltverifyMD5(oldPassword, password); if (!saltverifyMD5) { rs.setSuccess(false); rs.setMsg("輸入的舊密碼錯誤,請重新輸入!"); return rs; } }