⼀篇⽂章带你⼊门SpringSecurity实现密码加密和解码
⽂章⽬录
⼀、加密和解密
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 和 Secur
eRandom 实例。strength 越⼤,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。
不同于 Shiro 中需要⾃⼰处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就⾃带了盐,处理起来⾮常⽅便。
⽽ BCryptPasswordEncoder 就是 PasswordEncoder 接⼝的实现类。
3. PasswordEncoder
⼆、前期准备
⾸先新建⼀个 Spring Boot 项⽬,创建时引⼊ Spring Security 依赖和 web 依赖,如下图:
项⽬创建成功后,Spring Security 的依赖就添加进来了,在 Spring Boot 中我们加⼊的是 spring-boot-starter-security ,其实主要是这两个:
项⽬创建成功后,我们添加⼀个测试的 HelloController,内容如下:
@RestController
publicclass HelloController {
@GetMapping("/hello")
public String hello(){
return"hello";
}
}
接下来什么事情都不⽤做,我们直接来启动项⽬。
在项⽬启动过程中,我们会看到如下⼀⾏⽇志:
Using generated security password:30abfb1f-36e1-446a-a79b-f70024f589ab
这就是 Spring Security 为默认⽤户 user ⽣成的临时密码,是⼀个 UUID 字符串。
接下来我们去访问 localhost:8080/hello 接⼝,就可以看到⾃动重定向到登录页⾯了:
在登录页⾯,默认的⽤户名就是 user,默认的登录密码则是项⽬启动时控制台打印出来的密码,输⼊⽤户名密码之后,就登录成功了,登录成功后,我们就可以访问到 /hello 接⼝了。
在 Spring Security 中,默认的登录页⾯和登录接⼝,都是 /login ,只不过⼀个是 get 请求(登录页⾯),另⼀个是 post 请求(登录接⼝)。
⼤家可以看到,⾮常⽅便,⼀个依赖就保护了所有接⼝
有⼈说,你怎么知道知道⽣成的默认密码是⼀个 UUID 呢?
这个其实很好判断。
和⽤户相关的⾃动化配置类在 UserDetailsServiceAutoConfiguration ⾥边,在该类的 getOrDeducePassword ⽅法中,我们看到如下⼀⾏⽇志:
if(user.isPasswordGenerated()){
logger.info(String.format("%n%nUsing generated security password: %s%n", Password()));
}
毫⽆疑问,我们在控制台看到的⽇志就是从这⾥打印出来的。打印的条件是 isPasswordGenerated ⽅法返回 true,即密码是默认⽣成的。
进⽽我们发现,Password 出现在 SecurityProperties 中,在 SecurityProperties 中我们看到如下定义:
/**
* Default user name.
*/
private String name ="user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
private boolean passwordGenerated =true;
可以看到,默认的⽤户名就是 user,默认的密码则是 UUID,⽽默认情况下,passwordGenerated 也为 true。
⼆、⽤户配置
默认的密码有⼀个问题就是每次重启项⽬都会变,这很不⽅便。
在正式介绍数据库连接之前,先和⼤家介绍两种⾮主流的⽤户名/密码配置⽅案。
1. 配置⽂件
我们可以在 application.properties 中配置默认的⽤户名密码。
怎么配置呢?上⾯说的 SecurityProperties,默认的⽤户就定义在它⾥边,是⼀个静态内部类,我们如果要定义⾃⼰的⽤户名密码,必然是要去覆盖默认配置,我们先来看下 SecurityProperties 的定义:
@ConfigurationProperties(prefix ="spring.security")
publicclass SecurityProperties {}
这就很清晰了,我们只需要以 spring.security.user 为前缀,去定义⽤户名密码即可:
spring.security.user.name=yolo
spring.security.user.password=123
这就是我们新定义的⽤户名密码。
在 properties 中定义的⽤户名密码最终是通过 set ⽅法注⼊到属性中去的,这⾥我们顺便来看下 SecurityProperties.User#setPassword ⽅法:
public void setPassword(String password){
if(!StringUtils.hasLength(password)){
return;
}
this.passwordGenerated =false;
this.password = password;
}
从这⾥我们可以看到,application.properties 中定义的密码在注⼊进来之后,还顺便设置了 passwordGenerated 属性为 false,这个属性设置为 false 之后,控制台就不会打印默认的密码了。
此时重启项⽬,就可以使⽤⾃⼰定义的⽤户名/密码登录了。
2. 配置类
除了上⾯的配置⽂件这种⽅式之外,我们也可以在配置类中配置⽤户名/密码。
在配置类中配置,我们就要指定 PasswordEncoder 了,这是⼀个⾮常关键的东西。
PasswordEncoder 这个接⼝中就定义了三个⽅法:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword){
returnfalse;
}
}
(1)encode ⽅法⽤来对明⽂密码进⾏加密,返回加密之后的密⽂。
(2)matches ⽅法是⼀个密码校对⽅法,在⽤户登录的时候,将⽤户传来的明⽂密码和数据库中保存的密⽂密码作为参数,传⼊到这个⽅法中去,根据返回的 Boolean 值判断⽤户密码是否输⼊正确。
(3)upgradeEncoding 是否还要进⾏再次加密,这个⼀般来说就不⽤了。
通过下图我们可以看到 PasswordEncoder 的实现类:
具体的配置:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
springboot推荐算法
Instance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception {
auth.inMemoryAuthentication()
.
withUser("yolo")
.password("123").roles("admin");
}
}
(1) ⾸先我们⾃定义 SecurityConfig 继承⾃ WebSecurityConfigurerAdapter,重写⾥边的 configure ⽅法。
(2)⾸先我们提供了⼀个 PasswordEncoder 的实例,因为⽬前的案例还⽐较简单,因此我暂时先不给密码进⾏加密,所以返回NoOpPasswordEncoder 的实例即可。
(3)configure ⽅法中,我们通过 inMemoryAuthentication 来开启在内存中定义⽤户,withUser 中是⽤户名,password 中则是⽤户密码,roles 中是⽤户⾓⾊。
如果需要配置多个⽤户,⽤ and 相连。
在没有 Spring Boot 的时候,我们都是 SSM 中使⽤ Spring Security,这种时候都是在 XML ⽂件中配
置 Spring Security,既然是 XML ⽂件,标签就有开始有结束,现在的 and 符号相当于就是 XML 标签的结束符,表⽰结束当前标签,这是个时候上下⽂会回到 inMemoryAuthentication ⽅法中,然后开启新⽤户的配置。
配置完成后,再次启动项⽬,Java 代码中的配置会覆盖掉 XML ⽂件中的配置,此时再去访问 /hello 接⼝,就会发现只有 Java 代码中的⽤户名/密码才能访问成功。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。