在 Sprig Boot 简单整合 Spring Security
#1. Hello World
创建一个 Spring Boot 应用,并引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
编写一个控制器:
@RestController
public class TestController {
@RequestMapping("/hello")
public String hello() {
return "Hello SpringSecurity!";
}
}
然后直接启动项目,访问 http://localhost:8080/login (opens new window):
结果打开的是一个登录页面,其实这时候我们的请求已经被保护起来了,要想访问,需要先登录。
在这个案例中仅仅是引入了一个 Spring Security 的 starter 启动器,没有做任何的配置,而项目已经具有了权限认证。
Spring Security 默认提供了一个用户名为 user
的用户,其密码在控制台可以找到:
成功登录以后就可以正常访问了:
如果想要想修改配置,则应使用 spring.security.user.name
和 spring.security.user.password
。
在 Spring Boot 的配置文件中进行如下配置:
spring.security.user.name=tom
spring.security.user.password=123
此时启动项目,将只能通过自己配置的用户名和密码登录。
当然还可以通过配置类的方式进行配置,创建一个配置类去继承 WebSecurityConfigurerAdapter:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
}
}
警告
从 5.x 开始,强制性要求必须使用密码加密器(PasswordEncoder)对原始密码(注册密码)进行加密。
因此,如果忘记指定 PasswordEncoder 会导致执行时会出现 There is no PasswordEncoder mapped for the id "null"
异常。
重新启动项目测试一下。会发现登录不上,观察控制台:
这是因为我们在对密码加密的时候使用到了 BCryptPasswordEncoder 对象,而 Spring Security 在对密码比对的过程中不会『自己创建』加密器,因此,需要我们在 Spring IoC 容器中配置、创建好加密器的单例对象,以供它直接使用。
所以,我们还需要在容器中配置、创建加密器的单例对象(上面那个 new 理论上可以改造成注入):
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
再次重新启动一切正常。
#2. Password Encoder
之前有提及,Spring Security 升级到 5
版本后提高了安全要求:Spring Security 要求所有的密码的存储都『必须』是加密形式的。为此,我们必须要确保 Spring IoC 容器中有一个了 PasswordEncoder 的单例对象用以供 Spring Security 使用。
PasswordEncoder 在两处场景会被使用到:
-
当我们实现注册功能时,要将用户在前端页面输入的明文密码使用 PasswordEncoder 进行加密后存储、持久化。
-
当用户在登录时,Spring Security 会将用户在前端页面输入的明文密码使用 PasswordEncoder 进行加密之后,再和由我们提供的密码『标准答案』进行比对。
Spring Security 使用 PasswordEncoder 对你提供的密码进行加密。该接口中有两个方法:加密方法,是否匹配方法。
-
加密方法(encode)方法在用户注册时使用。在注册功能处,我们(程序员)需要将用户提供的密码加密后存储(至数据库)。
-
匹配方法 matches 方法是由 Spring Security 调用的。在登录功能处,Spring Security 要用它来比较登录密码和密码『标准答案』。
因此上面的配置改为如下形式更为合理:
@Slf4j
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String password = passwordEncoder().encode("123");
log.info(password);
auth.inMemoryAuthentication().withUser("tom")
.password(password)
.roles("admin");
}
}
Spring Security 内置的 Password Encoder 有:
加密算法名称 | PasswordEncoder |
---|---|
NOOP | NoOpPasswordEncoder.getInstance() |
SHA256 | new StandardPasswordEncoder() |
BCRYPT(官方推荐) | new BCryptPasswordEncoder() |
LDAP | new LdapShaPasswordEncoder() |
PBKDF2 | new Pbkdf2PasswordEncoder() |
SCRYPT | new SCryptPasswordEncoder() |
MD4 | new Md4PasswordEncoder() |
MD5 | new MessageDigestPasswordEncoder("MD5") |
SHA_1 | new MessageDigestPasswordEncoder("SHA-1") |
SHA_256 | new MessageDigestPasswordEncoder("SHA-256") |
上述 Password Encoder 中有一个『无意义』的加密器:NoOpPasswordEncoder 。它对原始密码没有做任何处理(现在也被标记为废弃)。
补充
记得使用 @SuppressWarnings("deprecation") 去掉 IDE 的警告信息。
#3. “消失”的登录功能
不知道大家有没有注意到,其实,我们的 Controller 中还没有写登录功能的相关代码(这和 Shiro 非常不同)。但是,之前的示例中,就已经有了完整的『登录』(甚至『退出』)功能,并且,Spring Security 似乎还能记住我们已经登陆过(当我们第二次访问页面时,它不会要求我们再次登录)!