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

 

posted on 2021-12-07 10:20  周文豪  阅读(1381)  评论(1编辑  收藏  举报