当前位置: 移动技术网 > IT编程>开发语言>Java > Spring Boot 中密码加密的两种方法

Spring Boot 中密码加密的两种方法

2020年09月03日  | 移动技术网IT编程  | 我要评论
先说一句:密码是无法解密的。大家也不要再问松哥微人事项目中的密码怎么解密了!密码无法解密,还是为了确保系统安全。今天松哥就来和大家聊一聊,密码要如何处理,才能在最大程度上确保我们的系统安全。1.为什么

先说一句:密码是无法解密的。大家也不要再问松哥微人事项目中的密码怎么解密了!

密码无法解密,还是为了确保系统安全。今天松哥就来和大家聊一聊,密码要如何处理,才能在最大程度上确保我们的系统安全。

1.为什么要加密

2011 年 12 月 21 日,有人在网络上公开了一个包含 600 万个 csdn 用户资料的数据库,数据全部为明文储存,包含用户名、密码以及注册邮箱。事件发生后 csdn 在微博、官方网站等渠道发出了声明,解释说此数据库系 2009 年备份所用,因不明原因泄露,已经向警方报案,后又在官网发出了公开道歉信。在接下来的十多天里,金山、网易、京东、当当、新浪等多家公司被卷入到这次事件中。整个事件中最触目惊心的莫过于 csdn 把用户密码明文存储,由于很多用户是多个网站共用一个密码,因此一个网站密码泄露就会造成很大的安全隐患。由于有了这么多前车之鉴,我们现在做系统时,密码都要加密处理。

这次泄密,也留下了一些有趣的事情,特别是对于广大程序员设置密码这一项。人们从 csdn 泄密的文件中,发现了一些好玩的密码,例如如下这些:

  • ppnn13%dkstfeb.1st 这段密码的中文解析是:娉娉袅袅十三余,豆蔻梢头二月初。
  • csbt34.ydhl12s 这段密码的中文解析是:池上碧苔三四点,叶底黄鹂一两声
  • ...

等等不一而足,你会发现很多程序员的人文素养还是非常高的,让人啧啧称奇。

2.加密方案

密码加密我们一般会用到散列函数,又称散列算法、哈希函数,这是一种从任何数据中创建数字“指纹”的方法。

散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,重新创建一个散列值。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。

我们常用的散列函数有 md5 消息摘要算法、安全散列算法(secure hash algorithm)。

但是仅仅使用散列函数还不够,单纯的只使用散列函数,如果两个用户密码明文相同,生成的密文也会相同,这样就增加的密码泄漏的风险。

为了增加密码的安全性,一般在密码加密过程中还需要加盐,所谓的盐可以是一个随机数也可以是用户名,加盐之后,即使密码明文相同的用户生成的密码密文也不相同,这可以极大的提高密码的安全性。

传统的加盐方式需要在数据库中有专门的字段来记录盐值,这个字段可能是用户名字段(因为用户名唯一),也可能是一个专门记录盐值的字段,这样的配置比较繁琐。

spring security 提供了多种密码加密方案,官方推荐使用 bcryptpasswordencoder,bcryptpasswordencoder 使用 bcrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 securerandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。

不同于 shiro 中需要自己处理密码加盐,在 spring security 中,bcryptpasswordencoder 就自带了盐,处理起来非常方便。

3.实践

3.1 codec 加密

commons-codec 是一个 apache 上的开源项目,用它可以方便的实现密码加密。松哥在 v 部落 项目中就是采用的这种方案(https://github.com/lenve/vblog)。在 spring security 还未推出 bcryptpasswordencoder 的时候,commons-codec 还是一个比较常见的解决方案。

所以,这里我先来给大家介绍下 commons-codec 的用法。

首先我们需要引入 commons-codec 的依赖:

<dependency>
  <groupid>commons-codec</groupid>
  <artifactid>commons-codec</artifactid>
  <version>1.11</version>
</dependency>

然后自定义一个 passwordencoder:

@component
public class mypasswordencoder implements passwordencoder {
  @override
  public string encode(charsequence rawpassword) {
    return digestutils.md5digestashex(rawpassword.tostring().getbytes());
  }

  @override
  public boolean matches(charsequence rawpassword, string encodedpassword) {
    return encodedpassword.equals(digestutils.md5digestashex(rawpassword.tostring().getbytes()));
  }
}

在 spring security 中,passwordencoder 专门用来处理密码的加密与比对工作,我们自定义 mypasswordencoder 并实现 passwordencoder 接口,还需要实现该接口中的两个方法:

  1. encode 方法表示对密码进行加密,参数 rawpassword 就是你传入的明文密码,返回的则是加密之后的密文,这里的加密方案采用了 md5。
  2. matches 方法表示对密码进行比对,参数 rawpassword 相当于是用户登录时传入的密码,encodedpassword 则相当于是加密后的密码(从数据库中查询而来)。

最后记得将 mypasswordencoder 通过 @component 注解标记为 spring 容器中的一个组件。

这样用户在登录时,就会自动调用 matches 方法进行密码比对。

当然,使用了 mypasswordencoder 之后,在用户注册时,就需要将密码加密之后存入数据库中,方式如下:

public int reg(user user) {
  ...
  //插入用户,插入之前先对密码进行加密
  user.setpassword(passwordencoder.encode(user.getpassword()));
  result = usermapper.reg(user);
  ...
}

其实很简单,就是调用 encode 方法对密码进行加密。完整代码大家可以参考 v 部落(https://github.com/lenve/vblog),我这里就不赘述了。

3.2 bcryptpasswordencoder 加密

但是自己定义 passwordencoder 还是有些麻烦,特别是处理密码加盐问题的时候。

所以在 spring security 中提供了 bcryptpasswordencoder,使得密码加密加盐变得非常容易。只需要提供 bcryptpasswordencoder 这个 bean 的实例即可,微人事就是采用了这种方案(),如下:

@bean
passwordencoder passwordencoder() {
  return new bcryptpasswordencoder(10);
}

创建 bcryptpasswordencoder 时传入的参数 10 就是 strength,即密钥的迭代次数(也可以不配置,默认为 10)。同时,配置的内存用户的密码也不再是 123 了,如下:

auth.inmemoryauthentication()
.withuser("admin")
.password("$2a$10$rmufxgq5ath4wovkuqyvuecpquseoxzyqilxzbz50dcersga.wyiq")
.roles("admin", "user")
.and()
.withuser("sang")
.password("$2a$10$euhbaomq4bpxtvovz33liehle3fu6nwqc9tdocxjxehyz4simqxtc")
.roles("user");

这里的密码就是使用 bcryptpasswordencoder 加密后的密码,虽然 admin 和 sang 加密后的密码不一样,但是明文都是 123。配置完成后,使用 admin/123 或者 sang/123 就可以实现登录。

本案例使用了配置在内存中的用户,一般情况下,用户信息是存储在数据库中的,因此需要在用户注册时对密码进行加密处理,如下:

@service
public class regservice {
  public int reg(string username, string password) {
    bcryptpasswordencoder encoder = new bcryptpasswordencoder(10);
    string encodepasswod = encoder.encode(password);
    return savetodb(username, encodepasswod);
  }
}

用户将密码从前端传来之后,通过调用 bcryptpasswordencoder 实例中的 encode 方法对密码进行加密处理,加密完成后将密文存入数据库。

4.源码浅析

最后我们再来稍微看一下 passwordencoder。

passwordencoder 是一个接口,里边只有三个方法:

public interface passwordencoder {
  string encode(charsequence rawpassword);
  boolean matches(charsequence rawpassword, string encodedpassword);
  default boolean upgradeencoding(string encodedpassword) {
    return false;
  }
}
  • encode 方法用来对密码进行加密。
  • matches 方法用来对密码进行比对。
  • upgradeencoding 表示是否需要对密码进行再次加密以使得密码更加安全,默认为 false。

spring security 为 passwordencoder 提供了很多实现:

但是老实说,自从有了 bcryptpasswordencoder,我们很少关注其他实现类了。

passwordencoder 中的 encode 方法,是我们在用户注册的时候手动调用。

matches 方法,则是由系统调用,默认是在 daoauthenticationprovider#additionalauthenticationchecks 方法中调用的。

protected void additionalauthenticationchecks(userdetails userdetails,
    usernamepasswordauthenticationtoken authentication)
    throws authenticationexception {
  if (authentication.getcredentials() == null) {
    logger.debug("authentication failed: no credentials provided");
    throw new badcredentialsexception(messages.getmessage(
        "abstractuserdetailsauthenticationprovider.badcredentials",
        "bad credentials"));
  }
  string presentedpassword = authentication.getcredentials().tostring();
  if (!passwordencoder.matches(presentedpassword, userdetails.getpassword())) {
    logger.debug("authentication failed: password does not match stored value");
    throw new badcredentialsexception(messages.getmessage(
        "abstractuserdetailsauthenticationprovider.badcredentials",
        "bad credentials"));
  }
}

可以看到,密码比对就是通过 passwordencoder.matches 方法来进行的。

关于 daoauthenticationprovider 的调用流程,大家可以参考 springsecurity 自定义认证逻辑的两种方式(高级玩法)一文。

好了,今天就和小伙伴们简单聊一聊 spring security 加密问题,小伙伴们要是有收获记得点个在看鼓励下松哥哦~

以上就是spring boot 中密码加密的两种方法的详细内容,更多关于spring boot 密码加密的资料请关注移动技术网其它相关文章!

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网