1.为什么要盐值加密

对于同一密码,同一加密算法会产生相同的hash值。这样,当用户进行身份验证时,对用户输入的明文密码应用相同的hash加密算法,得出一个hash值,然后使用该hash值和之前存储好的密文值进行对照,如果两个值相同,则密码认证成功,否则密码认证失败。出于更安全的考虑,即使两个用户输入的是相同的密码,也应该要保存为不同的密文,即使用户输入的是弱密码,也需要考虑进行增强,从而增加密码被攻破的难度。因此出现了加盐加密。

那么盐值加密是怎么加密的呢,用下面的图来解释:

加密过程(用户注册)

oc aes 加密 加盐 Salted___用户名

也就是说最终的密文是以下两个内容的函数

  1. 用户输入的密码明文
  2. 盐值

最后要将以下内容存入数据库:

  1. 用户的用户名
  2. 最终的密文
  3. 加密所用的盐值

校验过程(用户登录)

oc aes 加密 加盐 Salted___用户名_02

2.在Springboot项目中如何利用Shrio进行密码加密

1.在ShiroConfig中为自定义UserRealm对象添加密码匹配凭证管理器HashedCredentialsMatcher

部分代码如下:

//创建Realm 对象,需要用户自定义
@Bean(name = "UserRealm")
public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
    UserRealm userRealm = new UserRealm();
    userRealm.setCredentialsMatcher(matcher);
    return userRealm;
}


/**
 * 密码匹配凭证管理器
 *
 * @return
 */
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    // 采用MD5方式加密
    hashedCredentialsMatcher.setHashAlgorithmName("MD5");
    // 设置加密次数
    hashedCredentialsMatcher.setHashIterations(1024);
    return hashedCredentialsMatcher;
}

2.修改UserRealm认证中AuthenticationInfo

/**
     * 认证
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        /*  System.out.println("执行了=》doGetAuthenticationInfo");*/

        UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
        //连接真实的数据库 用户名:密码 数据库中取
        User user = userService.queryUserByname(usertoken.getUsername());

        // 获取盐值,即用户名
        ByteSource salt = ByteSource.Util.bytes(usertoken.getUsername());

        //没有当前用户
        //抛出一个异常 LoginController中的login方法
        if (user == null) {
            return null;
        }

        //密码验证:Shiro做
        //自动认证 第一个参数传递User
        // 最后一个参数为当前realm对象的名称,调用分类的getName()
        return new SimpleAuthenticationInfo(user, user.getPwd(), salt, getName());
    }

同样的是登录Controller和之前写法一样

//获得当前的用户
    Subject subject = SecurityUtils.getSubject();
    //封装用户的登录数据
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {   
        subject.login(token);
        //执行登录方法,如果没有异常就说明ok了
        return "redirect:/main.html";
    } catch (UnknownAccountException e) {
        model.addAttribute("msg", "用户名错误");
        return "index";
    } catch (IncorrectCredentialsException e) {
        model.addAttribute("msg", "密码错误");
        return "index";
    }

不过在注册界面我们就需要对密码进行盐值加密了,这里Shiro为我们提供了加密api

public String registerUser(@RequestBody User user) {

        // 将用户名作为盐值
        ByteSource salt = ByteSource.Util.bytes(user.getName());
        /*
         * MD5加密:
         * 使用SimpleHash类对原始密码进行加密。
         * 第一个参数代表使用MD5方式加密
         * 第二个参数为原始密码
         * 第三个参数为盐值,即用户名
         * 第四个参数为加密次数
         * 最后用toHex()方法将加密后的密码转成String
         * */
        String newPs = new SimpleHash("MD5", user.getPwd(), salt, 1024).toHex();
        user.setPwd(newPs);
        user.setPerms(user.getPerms());
        userService.registerUser(user);
        return "register";
    }

需要注意的是,如果数据库中的密码不是加密后的会报错。
而我们利用这种加密方式,上一个文章的手撸自动登录就GG了,因为存入session中的是加密过后的密码,不能利用加密后的密码直接登录。