除了InMemoryUserDetailsManager,Spring Security还提供另一个UserDetailsService实现类: JdbcUserDetailsManager。 JdbcUserDetailsManager帮助我们以JDBC的方式对接数据库和Spring Security,它设定了一个默认的数据库模型,只要遵从这个模型,在简便性上,JdbcUserDetailsManager甚至可以媲美InMemoryUserDetailsManager。
1.配置数据库
引入JDBC和MySQL两个必要依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
其他依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在application.yml中配置数据库连接参数。
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springDemo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
username: root
password: root
JdbcUserDetailsManager设定了一个默认的数据库模型,Spring Security将该模型定义在/org/springframework/security/core/userdetails/jdbc/users.ddl内
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);
JdbcUserDetailsManager需要两个表,其中users表用来存放用户名、密码和是否可用三个信息,authorities表用来存放用户名及其权限的对应关系。 将其复制到MySQL命令窗口执行时,会报错,因为该语句是用hsqldb创建的,而MySQL不支持varchar_ignorecase这种类型。怎么办呢?很简单,将varchar_ignorecase改为MySQL支持的varchar即可。
create table users(username varchar(50) not null primary key,password varchar(500) not null,enabled boolean not null);
create table authorities (username varchar(50) not null,authority varchar(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
运行建表语句,得到authorities、users两个表。
2.编码实现
下面构建一个JdbcUserDetailsManager实例,让Spring Security使用数据库来管理用户。
@Bean
public UserDetailsService userDetailsService() {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);
manager.createUser(User.withUsername("user").password("123").roles("USER").build());
manager.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
return manager;
}
JdbcUserDetailsManager与InMemoryUserDetailsManager在用法上没有太大区别,只是多了设置DataSource的环节。Spring Security 通过 DataSource 执行设定好的命令。例如,此处的createUser函数实际上就是执行了下面的SQL语句。
insert into users(username, password, enabled) values (?,?,?)
查看 JdbcUserDetailsManager的源代码可以看到更多定义好的 SQL 语句,诸如deleteUserSql、updateUserSql等,这些都是JdbcUserDetailsManager与数据库实际交互的形式。当然,JdbcUserDetailsManager 也允许我们在特殊情况下自定义这些 SQL 语句,如有必要,调用对应的 setXxxSql方法即可。现在重启服务,看看在数据库中Spring Security生成了哪些数据
authorities表的authority字段存放的是前面设定的角色,只是会被添上“ROLE_”前缀。下面尝试通过SQL命令创建一个测试账号。
insert into users values ("test", "123", 1);
insert into authorities values("test", "ROLE_USER");
清空缓存并使用测试账号访问系统,发现可以访问user路由,但不能访问admin路由,与预期的行为一致。到目前为止,一切都工作得很好,但是只要我们重启服务,应用就会报错。这是因为users表在创建语句时,username字段为主键,主键是唯一不重复的,但重启服务后会再次创建admin和user,导致数据库报错(在内存数据源上不会出现这种问题,因为重启服务后会清空username字段中的内容)。所以如果需要在服务启动时便生成部分用户,那么建议先判断用户名是否存在。
@Bean
public UserDetailsService userDetailsService() {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);
if (!manager.userExists("user")) {
manager.createUser(User.withUsername("user").password("123").roles("USER").build());
}
if (!manager.userExists("admin")) {
manager.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
}
return manager;
}
WebSecurityConfigurerAdapter定义了三个configure。
我们只用到了一个参数,用来接收 HttpSecurity对象的配置方法。另外两个参数也有各自的用途,其中,AuthenticationManagerBuilder的configure同样允许我们配置认证用户。使用方法大同小异,这里不再赘述。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
@Override
public void configure(WebSecurity web) throws Exception {
}
当使用Spring Security默认数据库模型应对各种用户系统时,难免灵活性欠佳。尤其是在对现有的系统做Spring Security嵌入时,原本的用户数据已经固定,为了适配Spring Security而在数据库层面进行修改显然得不偿失。强大而灵活的Spring Security对这方面进行了改进。