写在前面
承接上文,上文讲到配置体系,今天主要讲解认证体系。感兴趣的小伙伴可以点击头像查看我专栏和历史文章。
Spring Security 用户认证体系
回到用户认证场景。因为 Spring Security 默认提供的用户名是固定的,而密码会随着每次应用程序的启动而变化,所以很不灵活。在 Spring Boot 中。
我们可以通过在 application.yml 配置文件中添加如下所示的配置项来改变这种默认行为:
spring:
security:
user:
name: spring
password: spring_password
我们可以使用上述用户名和密码完成登录。基于配置文件的用户信息存储方案简单直接,但显然也缺乏灵活性,因为我们无法在系统运行时动态加载对应的用户名和密码。
因此,在现实中,我们主要还是通过使用 WebSecurityConfigurerAdapter 配置类来改变默认的配置行为。
前面我们说过,知道可以通过 WebSecurityConfigurerAdapter 类的 configure(HttpSecurity http) 方法来完成认证。
认证过程涉及 Spring Security 中用户信息的交互,我们可以通过继承 WebSecurityConfigurerAdapter 类并且覆写其中的configure(AuthenticationManagerBuilder auth) 的方法来完成对用户信息的配置工作。请注意这是两个不同的 configure() 方法。
两种方案
针对 WebSecurityConfigurer 配置类,我们首先需要明确配置的内容。实际上,初始化用户信息非常简单,只需要指定用户名(Username)、密码(Password)和角色(Role)这三项数据即可。在 Spring Security 中,基于 AuthenticationManagerBuilder 工具类为开发人员提供了基于内存、JDBC、LDAP 等多种验证方案。
接下来,我们就围绕 AuthenticationManagerBuilder 提供的功能来实现多种用户信息存储方案。
基于内存的用户信息存储方案
实现方法就是调用 AuthenticationManagerBuilder 的 inMemoryAuthentication 方法,示例代码如下:
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication()
.withUser("spring_user").password("password1").roles("USER")
.and()
.withUser("spring_admin").password("password2").roles("USER", "ADMIN");
}
从上面的代码中,我们可以看到系统中存在“spring_user”和“spring_admin”这两个用户,其密码分别是"password1"和"password2",在角色上也分别代表着普通用户 USER 以及管理员 ADMIN。
请注意,这里的 roles() 方法背后使用的还是authorities() 方法。通过 roles() 方法,Spring Security 会在每个角色名称前自动添加“ROLE_”前缀,所以我们也可以通过如下所示的代码实现同样的功能:
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication()
.withUser("spring_user").password("password1").authorities("ROLE_USER")
.and()
.withUser("spring_admin").password("password2").authorities("ROLE_USER", "ROLE_ADMIN");
}
可以看到,基于内存的用户信息存储方案实现也比较简单,但同样缺乏灵活性,因为用户信息是写死在代码里的。所以,我们接下来就要引出另一种更为常见的用户信息存储方案——数据库存储。
基于数据库的用户信息存储方案
既然是将用户信息存储在数据库中,势必需要创建表结构。我们可以在 Spring Security 的源文件(org/springframework/security/core/userdetails/jdbc/users.ddl)中找到对应的 SQL 语句,如下所示:
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
一旦我们在自己的数据库中创建了这两张表,并添加了相应的数据,就可以直接通过注入一个 DataSource 对象进行用户数据的查询,如下所示:
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " + "where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " + "where username=?")
.passwordEncoder(new BCryptPasswordEncoder());
}
这里使用了 AuthenticationManagerBuilder 的 jdbcAuthentication 方法来配置数据库认证方式,内部则使用了 JdbcUserDetailsManager 这个工具类。在该类中,就定义了各种用于数据库查询的 SQL 语句,以及使用 JdbcTemplate 完成数据库访问的具体实现方法。
请你注意,这里我们用到了一个passwordEncoder() 方法,这是 Spring Security 中提供的一个密码加解密器,我们会在“密码安全:Spring Security 中包含哪些加解密技术?”一讲中进行详细的讨论。
总结
我通过两篇文章介绍了如何使用 Spring Security 构建用户认证体系的系统方法。
在 Spring Security 中,认证相关的功能都是可以通过配置体系进行定制化开发和管理的。通过简单的配置方法,我们可以组合使用 HTTP 基础认证和表单登录认证,也可以分别基于内存以及基于数据库方案来存储用户信息,这些功能都是 Spring Security 内置的。