客户端信息存入数据库

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

涉及客户端信息保存的接口主要是 ClientDetailsService,这个接口主要有两个实现类,如图 15-18 所示。

image 2024 04 16 12 46 29 011
Figure 1. 图15-18 ClientDetailsService 的实现类

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 所示。

image 2024 04 16 12 51 30 163
Figure 2. 图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 小节中的案例相比,这里的变化主要在四个方面

  1. 注入 DataSource 实例。

  2. 向 Spring 容器注册一个 JdbcClientDetailsService 实例。

  3. 在 AuthorizationServerTokenServices 实例中除去令牌有效期设置,令牌有效期将从数据库中加载。

  4. 在 configure(ClientDetailsServiceConfigurer) 方法中直接配置 JdbcClientDetailsService 实例即可,项目启动后会自动从数据库中加载客户端信息。

配置完成后,重启授权服务器再去进行授权测试即可。