添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

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

本文是 Spring Security 系列的第 20 篇,阅读本系列前面的文章有助于更好的理解本文:

  1. 挖一个大坑,Spring Security 开搞!
  2. 松哥手把手带你入门 Spring Security,别再问密码怎么解密了
  3. 手把手教你定制 Spring Security 中的表单登录
  4. Spring Security 做前后端分离,咱就别做页面跳转了!统统 JSON 交互
  5. Spring Security 中的授权操作原来这么简单
  6. Spring Security 如何将用户数据存入数据库?
  7. Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!
  8. Spring Boot + Spring Security 实现自动登录功能
  9. Spring Boot 自动登录,安全风险要怎么控制?
  10. 在微服务项目中,Spring Security 比 Shiro 强在哪?
  11. SpringSecurity 自定义认证逻辑的两种方式(高级玩法)
  12. Spring Security 中如何快速查看登录用户 IP 地址等信息?
  13. Spring Security 自动踢掉前一个登录用户,一个配置搞定!
  14. Spring Boot + Vue 前后端分离项目,如何踢掉已登录用户?
  15. Spring Security 自带防火墙!你都不知道自己的系统有多安全!
  16. 什么是会话固定攻击?Spring Boot 中要如何防御会话固定攻击?
  17. 集群化部署,Spring Security 要如何处理 session 共享?
  18. 松哥手把手教你在 SpringBoot 中防御 CSRF 攻击!so easy!
  19. 要学就学透彻!Spring Security 中 CSRF 防御源码解析

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.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 的实例即可,微人事就是采用了这种方案(https://github.com/lenve/vhr),如下:

@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 Security 系列的第 20 篇,阅读本系列前面的文章有助于更好的理解本文:挖一个大坑,Spring Security 开搞!松哥手把手带你入门 Spring Security,别再问密码怎么解密了手把手教你定制 Spring Security 中的表单登录Spring Secur
Spring Boot 项目密码如何加密 先说一句:密码是采用非对称加密是无法解密的。密码无法解密,还是为了确保系统安全。今天就来和大家聊一聊,密码要如何处理,才能在最大程度上确保我们的系统安全密码加密我们一般会用到散列函数,又称散列算法、哈希函数,这是一种从任何数据创建数字“指纹”的方法。 散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,重新创建一个散列值。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域很少出现散列冲突。在散列表
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族的成员。Spring Security为基于J2EE企业应用软件提供了全面安全服务。 特别是使用领先的J2EE解决方案-spring框架开发的企业软件项目。 安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括**用户认证(Authentication)和用户授权 ** **(Authorization)**两个部分,这两点也是 Spring 我们与客户端的接口交互,为了更高的安全性,我们可能需要对接口加密(请求参数加密,服务端解密)、返回信息加密(服务端加密,客户端解密),但是也不是所有的接口都这样,有些接口可能不需要,我们可以使用注解来轻松达到此要求。 将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用C...
服务端、客户端和H5统一拦截加解密,网上有成熟方案,也可以按其他服务实现的加解密流程来搞; 使用AES放松加密,考虑到H5端存储密钥安全性相对来说会低一些,故分针对H5和安卓、IOS分配两套密钥; 本次涉及客户端和服务端的整体改造,经讨论,新接口统一加 /secret/ 前缀来区分
此篇文章是在Shiro整合Spring Boot入门的基础上写的,如果你没有shiro基础看不懂的话,可以去看看。 1、给Realm添加CredentialsMatcher @Bean public UserRealm userRealm() { UserRealm userRealm = new UserRealm(); // 设置加密算法 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatche
Spring Boot 通常使用 Spring Security 这个框架来实现密码加密Spring Security 提供了一些内置的 PasswordEncoder 接口来帮助你实现密码加密。 你可以在配置文件配置 Spring Security 使用哪种 PasswordEncoder。例如,你可以使用 BCryptPasswordEncoder 来实现密码的 BCrypt 加密。 具体来说,你需要在你的配置类上加上 @EnableWebSecurity 注解,然后在你的配置类配置一个 AuthenticationManagerBuilder 对象,并调用其 passwordEncoder() 方法来设置 PasswordEncoder。 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(passwordEncoder()) .withUser("user").password(passwordEncoder().encode("password")).roles("USER"); 在这个例子,我们使用了 BCryptPasswordEncoder 来对密码进行加密,并将加密后的密码存储到内存。 当用户登录时,Spring Security 会使用相同的 PasswordEncoder 来对用户提交的密码进行加密,然后与存储在内存密码进行比较。如果两者相同,则认为用户输入的密码正确,登录成功;如果不同,则登录失败。 希望这个回答对你有帮助!
CSDN-Ada助手: 非常感谢博主的用心分享,这篇博客让我对在 MySQL 排序中使用索引有了更深入的了解。博主的写作风格清晰明了,讲解细致入微,让我收益颇丰。希望博主能继续创作,分享更多有价值的知识,让更多人受益。再次感谢博主! 为了方便博主创作,提高生产力,CSDN上线了AI写作助手功能,就在创作编辑器右侧哦~(https://mp.csdn.net/edit?utm_source=blog_comment_recall )诚邀您来加入测评,到此(https://activity.csdn.net/creatActivity?id=10450&utm_source=blog_comment_recall)发布测评文章即可获得「话题勋章」,同时还有机会拿定制奖牌。 Spring Boot+OAuth2,一个注解搞定单点登录! 42441715: 哪位大神知道为什么我自服务配置了security.oauth2.client.user-authorization-uri,但是没有跳转到授权服务的登陆页面?直接返回unauthorized未授权信息,是因为我过了网关吗 Spring Boot 分离配置文件的 N 种方式 qq_36620218: 又来找你学习啦老同学 还在用 OpenFeign?来试试 SpringBoot3 中的这个新玩意! chhb.zhou: 这个和openFeign比有啥优势,感觉更繁琐了 Spring Boot 整合流程引擎 Flowable,so easy ihcnam: mysql换成8.0版本,然后数据库引擎切换成innoDB解决了