账户密码存储的安全性是一个很老的话题,但还是会频频发生,一般的做法是 SHA256(userInputpwd+globalsalt+usersalt) 并设置密码时时要求长度与大小写组合,一般这样设计可以满足绝大部分的安全性需求。更复杂一些的方案有组合算法签名(比如:SHA256 + BCRYPT 组合 ) , 两步认证,Password Hash 等。

在之前集成  spring-security-oauth2 搭建 OAuth2.0 服务,依赖项 Spring Security 5 默认引入了更安全的加/解密机制,如果之前程序使用纯文本的方式存储用户密码与 Client 的密钥或低版本升级到 Spring Security 5 后可能会出现如下错误。

Encoded password does not look like BCrypt
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)

Spring Security 5 对 PasswordEncoder 做了相关的重构,提供了 Password Hash 算法的实现(bCrypt, PBKDF2, SCrypt 等是最常用的几种密码 Hash 算法),将密码编码之后的 hash 值和加密方式一起存储,原先默认配置的 PlainTextPasswordEncoder 明文密码被移除了(本身明文存储密码也是不合适的一种方式,只适用与测试环境)。

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}

createDelegatingPasswordEncoder 方法定义了众多密码密码编码方式的集合,可以通过使用 PasswordEncoderFactories 类创建一个 DelegatingPasswordEncoder 的方式来解决这个问题。

Reverting to NoOpPasswordEncoder is not considered to be secure. You should instead migrate to using DelegatingPasswordEncoder to support secure password encoding. https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#troubleshooting

/**
* Used for creating {@link PasswordEncoder} instances
* @author Rob Winch
* @since 5.0
*/
public class PasswordEncoderFactories { /**
* Creates a {@link DelegatingPasswordEncoder} with default mappings. Additional
* mappings may be added and the encoding will be updated to conform with best
* practices. However, due to the nature of {@link DelegatingPasswordEncoder} the
* updates should not impact users. The mappings current are:
*
* <ul>
* <li>bcrypt - {@link BCryptPasswordEncoder} (Also used for encoding)</li>
* <li>ldap - {@link LdapShaPasswordEncoder}</li>
* <li>MD4 - {@link Md4PasswordEncoder}</li>
* <li>MD5 - {@code new MessageDigestPasswordEncoder("MD5")}</li>
* <li>noop - {@link NoOpPasswordEncoder}</li>
* <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
* <li>scrypt - {@link SCryptPasswordEncoder}</li>
* <li>SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
* <li>SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
* <li>sha256 - {@link StandardPasswordEncoder}</li>
* </ul>
*
* @return the {@link PasswordEncoder} to use
*/
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder()); return new DelegatingPasswordEncoder(encodingId, encoders);
} private PasswordEncoderFactories() {}
}
Password Encoding

使用 BCryptPasswordEncoder 编码(默认

//    @Bean
// public PasswordEncoder passwordEncoder(){
// return new BCryptPasswordEncoder();
// } @Bean
PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
} /**
* 配置授权的用户信息
* @param authenticationManagerBuilder
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { // authenticationManagerBuilder.inMemoryAuthentication()
// .withUser("irving")
// .password(passwordEncoder().encode("123456"))
// .roles("read"); authenticationManagerBuilder.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}

使用 PasswordEncoderFactories 类提供的默认编码器,存储密码的格式如下所示( {id}encodedPassword ),然后在加密后的密码前添加 Password Encoder 各自的标识符

{bcrypt}$2a$10$oenCzSR.yLibYMDwVvuCaeIlSIqsx0TBY1094.jQ3wgPEXzTrA52.
public class TestBCryptPwd {

    @Bean
PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
} @Bean
public PasswordEncoder bcryptPasswordEncoder(){
return new BCryptPasswordEncoder();
} @Bean
public PasswordEncoder pbkdf2PasswordEncoder(){
return new Pbkdf2PasswordEncoder();
} @Bean
public PasswordEncoder scryptPasswordEncoder(){
return new SCryptPasswordEncoder();
} @Test
public void testPasswordEncoder() {
String pwd = passwordEncoder().encode("123456");
String bcryptPassword = bcryptPasswordEncoder().encode("123456");
String pbkdf2Password = pbkdf2PasswordEncoder().encode("123456");
String scryptPassword = scryptPasswordEncoder().encode("123456");
System.out.println(pwd +"\n"+bcryptPassword +"\n"+pbkdf2Password+"\n"+scryptPassword); /*
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 1
{noop}password 2
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 3
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= 4
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 5
*/
}
}
Password Matching
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder;
String result = encoder.encode("123456");
assertTrue(encoder.matches("123456", result));
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("123456");
assertTrue(encoder.matches("123456", result));
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("123456");
assertTrue(encoder.matches("123456", result));

示列

    /**
* 修改用户密码
*
* @param oldPwd
* @param newPwd
* @param userName
* @return
*/
@Override
public Users modifyPwd(String oldPwd, String newPwd, String userName) {
Users user = this.userRepository.findByUsername(userName);
//验证用户是否存在
if (user == null) {
throw new UserFriendlyException("用户不存在!");
}
//验证原密码是否正确
if (!passwordEncoder.matches(oldPwd, user.getPassword())) {
throw new UserFriendlyException("原密码不正确!");
}
//修改密码
user.setPassword(passwordEncoder.encode(newPwd));
return this.userRepository.save(user);
}

应该使用哪一种Password Hash?[引用]

PBKDF2、BCRYPT、SCRYPT 曾经是最常用的三种密码Hash算法,至于哪种算法最好,多年以来密码学家们并无定论。但可以确定的是,这三种算法都不完美,各有缺点。其中PBKDF2因为计算过程需要内存少所以可被GPU/ASIC加速,BCRYPT不支持内存占用调整且容易被FPGA加速,而SCRYPT不支持单独调整内存或计算时间占用且可能被ASIC加速并有被旁路攻击的可能。

2013年NIST(美国国家标准与技术研究院)邀请了一些密码学家一起,举办了密码hash算法大赛(Password Hashing Competition),意在寻找一种标准的用来加密密码的hash算法,并借此在业界宣传加密存储用户密码的重要性。大赛列出了参赛算法可能面临的攻击手段:

  • [X] 加密算法破解(原值还原、哈希碰撞等,即应满足Cryptographic Hash的第2、3、4条特性);

  • [X] 查询表/彩虹表攻击;
  • [X] CPU优化攻击;
  • [X] GPU、FPGA、ASIC等专用硬件攻击;
  • [X] 旁路攻击;

最终在2015年7月,Argon2算法赢得了这项竞赛,被NIST认定为最好的密码hash算法。不过因为算法过新,目前还没听说哪家大公司在用Argon2做密码加密。

REFER:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-password-encoding
https://stackoverflow.com/questions/49582971/encoded-password-does-not-look-like-bcrypt
https://www.baeldung.com/spring-security-5-default-password-encoder
https://www.cnkirito.moe/spring-security-6/
https://stackoverflow.com/questions/6832445/how-can-bcrypt-have-built-in-salts
https://www.cnblogs.com/xinzhao/p/6035847.html
http://www.cnblogs.com/cnblogsfans/p/5112167.html
https://github.com/KaiZhang890/store-password-safely

关于 Spring Security 5 默认使用 Password Hash 算法的更多相关文章

  1. spring security之 默认登录页源码跟踪

    spring security之 默认登录页源码跟踪 ​ 2021年的最后2个月,立个flag,要把Spring Security和Spring Security OAuth2的应用及主流程源码研究透 ...

  2. 追踪分布式Memcached默认的一致性hash算法

    <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255) ...

  3. Spring Security报异常 Encoded password does not look like BCrypt

    控制台报错: Encoded password does not look like BCrypt 意思是前端传回去的密码格式与数据库里的密码格式不匹配,这样即使密码正确也无法校验.自然也就无法登录. ...

  4. springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

    项目security_simple(认证授权项目) 1.新建springboot项目 这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  ...

  5. Spring Security教程系列(一)基础篇-2

    第 4 章 自定义登陆页面 Spring Security虽然默认提供了一个登陆页面,但是这个页面实在太简陋了,只有在快速演示时才有可能它做系统的登陆页面,实际开发时无论是从美观还是实用性角度考虑,我 ...

  6. 【JavaEE】SSH+Spring Security整合及example

    到前文为止,SSH的基本框架都已经搭建出来了,现在,在这基础上再加上权限控制,也就是Spring Security框架,和前文的顺序一样,先看看需要加哪些库. 1. pom.xml Spring Se ...

  7. SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解

    前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Sprin ...

  8. 使用 Spring Security 保护 Web 应用的安全

    安全一直是 Web 应用开发中非常重要的一个方面.从安全的角度来说,需要考虑用户认证和授权两个方面.为 Web 应用增加安全方面的能力并非一件简单的事情,需要考虑不同的认证和授权机制.Spring S ...

  9. Spring Security(19)——对Acl的支持

    目录 1.1           准备工作 1.2           表功能介绍 1.2.1     表acl_sid 1.2.2     表acl_class 1.2.3     表acl_obj ...

随机推荐

  1. 微信小程序——微信卡券的领取和查看

    这里大致介绍下微信卡券的一些常见问题,不再介绍具体技术了,相关接口详见微信卡券. 1. 会员卡跟卡券一样么? 这个是一样的,至少在前端是一样处理的,最多也就是卡券设置展示不同.对于微信卡券领取和查看的 ...

  2. Maven Nexus3 安装,私服搭建

    为啥搭建Maven私服? 如果没有私服,我们所需的所有构件都需要通过maven的中央仓库和第三方的Maven仓库下载到本地,而一个团队中的所有人都重复的从maven仓库下载构件无疑加大了仓库的负载和浪 ...

  3. Python开发——数据类型【集合】

    集合的定义 由一个或多个确定的元素所构成的整体 可变集合 s=set('hello') print(s) # {'e', 'l', 'o', 'h'} s=set(['alex','alex','Lu ...

  4. 探索未知种族之osg类生物--渲染遍历之GraphicsContext::runOperations

    osg::GraphicsContext::runOperations().我们先来看一下这个函数的执行过程. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...

  5. 设置textfield 文字左边距

    默认情况下,当向textField输入文字时,文字会紧贴在textField左边框上.我们可以通过设置textField的leftView,设置一个只有宽度的leftView.这样还不够,因为默认le ...

  6. python基础之Day6

    一.元组 定义:t=(1,2,3,4) 总结:存多个值,值为任意类型 只有读的需求,没有改的需求 有序,不可变(元组里每个值对应的索引内存地址不能变) 在元素个数相同的情况下,元组比列表更节省空间 二 ...

  7. finereport 下拉复选框多选

  8. Java多线程系列2 线程常见方法介绍

    守护线程 执行一些非业务方法,比如gc.当全部都是守护线程的时候,jvm退出 线程优先级  设置线程优先级:setPriority(int priorityLevel).参数priorityLevel ...

  9. 牛客练习赛31 D 神器大师泰兹瑞与威穆 STL,模拟 A

    牛客练习赛31 D 神器大师泰兹瑞与威穆 https://ac.nowcoder.com/acm/contest/218/D 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 26214 ...

  10. C++ MFC棋牌类小游戏day4

    根据昨天的计划,今天开始做下面的内容. 1.鼠标点击事件 2.点击坐标进行处理.(坐标转换) 3.判断选中的位置是否有效. 4.确定选中的棋子,设置棋子的状态和棋子所在坐标的状态. 5.判断移动是否有 ...