客户端信息存入数据库
在 15.52.1 小节搭建的授权服务器中,客户端信息是直接存诸在内存中的。然而在实际项目中,这种方式并不可取,一方面客户端信息无法实现动态添加与删除,另一方面硬编码的客户端信息也不好维护,所以我们需要将客户端信息存入数据库中。
涉及客户端信息保存的接口主要是 ClientDetailsService,这个接口主要有两个实现类,如图 15-18 所示。

InMemoryClientDetailsService 就是将客户端信息存入内存中,也就是我们之前案例所采用的存储方式:JdbcClientDetailsService 则是将客户端信息存入数据库中。
由于官方没有给出使用 JdbcClientDetailsService 存储客户端信息时的数据库脚本,所以我们可以根据 JdbcClientDetailsService 中定义的 SQL 来分析出数据库表结构。JdbcClientDetailsService 部分源码:
private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
private static final String CLIENT_FIELDS = "client_secret, " + CLIENT_FIELDS_FOR_UPDATE;
根据这两个属性就能确定数据库字段名,最终分析出来的 SQL 如下:
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在数据库中执行该段 SQL 脚本,并将一开始配置在代码中的客户端信息录入数据库中,如图 15-19 所示。

然后在授权服务器 auth-server 中添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在 application.properties 中配置一下数据库连接信息:
spring.datasource.url=jdbc:mysql:///security15?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.password=123
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.main.allow-bean-definition-overriding=true
最后一条配置是允许 Bean 的覆盖,否则我自己创建的 ClientDetailsService 将会和系统创建的 ClientDetailsService 实例相冲突。
接下来配置 ClientDetailsService 实例,代码如下:
@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
DataSource dataSource;
@Bean
ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService());
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore);
return services;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
clients.withClientDetails(clientDetailsService());
}
// 省略其它
}
和 15.5.2.1 小节中的案例相比,这里的变化主要在四个方面
-
注入 DataSource 实例。
-
向 Spring 容器注册一个 JdbcClientDetailsService 实例。
-
在 AuthorizationServerTokenServices 实例中除去令牌有效期设置,令牌有效期将从数据库中加载。
-
在 configure(ClientDetailsServiceConfigurer) 方法中直接配置 JdbcClientDetailsService 实例即可,项目启动后会自动从数据库中加载客户端信息。
配置完成后,重启授权服务器再去进行授权测试即可。