参考文章: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; } }