SpringBoot3集成Oauth2.1——6数据库存储客户端信息
获取SQL文件oauth2-authorization-schema.sqloauth2-authorization-consent-schema.sqloauth2-registered-client-schema.sql2添加客户端信息2.1SQL语句添加(不推荐)如下所示,可以提看到,其他的一些字段,基本都是我们知道的字符串,但是其中client_settings,则很难知道填写什么。下面是
1获取SQL语句
获取SQL文件
oauth2-authorization-schema.sql
oauth2-authorization-consent-schema.sql
oauth2-registered-client-schema.sql
-- 存储用户对客户端的授权决策,避免重复授权
CREATE TABLE oauth2_authorization_consent (
registered_client_id VARCHAR(100) NOT NULL COMMENT '注册客户端ID',
principal_name VARCHAR(200) NOT NULL COMMENT '用户身份标识',
authorities VARCHAR(1000) NOT NULL COMMENT '用户授权的权限范围',
PRIMARY KEY (registered_client_id, principal_name)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='OAuth 2.1 授权同意记录表';
-- OAuth 2.1 客户端注册表:存储第三方应用的注册信息和安全配置
CREATE TABLE oauth2_registered_client (
id VARCHAR(100) NOT NULL COMMENT '客户端唯一标识(内部使用)',
client_id VARCHAR(100) NOT NULL COMMENT '客户端公开标识符(如应用ID)',
client_id_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '客户端ID颁发时间',
client_secret VARCHAR(200) DEFAULT NULL COMMENT '客户端密钥(需加密存储)',
client_secret_expires_at TIMESTAMP DEFAULT NULL COMMENT '客户端密钥过期时间',
client_name VARCHAR(200) NOT NULL COMMENT '客户端显示名称',
client_authentication_methods VARCHAR(1000) NOT NULL COMMENT '客户端认证方式(逗号分隔,如client_secret_basic,client_secret_post)',
authorization_grant_types VARCHAR(1000) NOT NULL COMMENT '支持的授权类型(逗号分隔,如authorization_code,refresh_token)',
redirect_uris VARCHAR(1000) DEFAULT NULL COMMENT '授权回调URI(多个URI使用逗号分隔)',
post_logout_redirect_uris VARCHAR(1000) DEFAULT NULL COMMENT '注销后重定向URI',
scopes VARCHAR(1000) NOT NULL COMMENT '客户端默认申请的权限范围',
client_settings VARCHAR(2000) NOT NULL COMMENT '客户端配置(JSON格式,如require-proof-key=true)',
token_settings VARCHAR(2000) NOT NULL COMMENT '令牌配置(JSON格式,如access-token-time-to-live=PT1H)',
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='OAuth 2.1 客户端注册信息表';
-- OAuth 2.1 授权记录表:存储授权码、访问令牌、刷新令牌等各类令牌的状态和元数据
CREATE TABLE oauth2_authorization (
id VARCHAR(100) NOT NULL COMMENT '授权记录唯一标识',
registered_client_id VARCHAR(100) NOT NULL COMMENT '关联的客户端ID',
principal_name VARCHAR(200) NOT NULL COMMENT '资源所有者(用户)标识',
authorization_grant_type VARCHAR(100) NOT NULL COMMENT '授权类型(authorization_code, client_credentials, refresh_token等)',
authorized_scopes VARCHAR(1000) DEFAULT NULL COMMENT '授权的权限范围(逗号分隔)',
attributes TEXT DEFAULT NULL COMMENT '额外属性(JSON格式)',
state VARCHAR(500) DEFAULT NULL COMMENT 'OAuth2状态参数(用于防止CSRF)',
-- 授权码相关字段
authorization_code_value TEXT DEFAULT NULL COMMENT '授权码值(Base64编码)',
authorization_code_issued_at TIMESTAMP DEFAULT NULL COMMENT '授权码颁发时间',
authorization_code_expires_at TIMESTAMP DEFAULT NULL COMMENT '授权码过期时间',
authorization_code_metadata TEXT DEFAULT NULL COMMENT '授权码元数据(JSON格式)',
-- 访问令牌相关字段
access_token_value TEXT DEFAULT NULL COMMENT '访问令牌值(Base64编码)',
access_token_issued_at TIMESTAMP DEFAULT NULL COMMENT '访问令牌颁发时间',
access_token_expires_at TIMESTAMP DEFAULT NULL COMMENT '访问令牌过期时间',
access_token_metadata TEXT DEFAULT NULL COMMENT '访问令牌元数据(JSON格式)',
access_token_type VARCHAR(100) DEFAULT NULL COMMENT '令牌类型(Bearer, MAC等)',
access_token_scopes VARCHAR(1000) DEFAULT NULL COMMENT '访问令牌权限范围',
-- ID令牌(OIDC)相关字段
oidc_id_token_value TEXT DEFAULT NULL COMMENT 'ID令牌值(JWT格式)',
oidc_id_token_issued_at TIMESTAMP DEFAULT NULL COMMENT 'ID令牌颁发时间',
oidc_id_token_expires_at TIMESTAMP DEFAULT NULL COMMENT 'ID令牌过期时间',
oidc_id_token_metadata TEXT DEFAULT NULL COMMENT 'ID令牌元数据(JSON格式)',
-- 刷新令牌相关字段
refresh_token_value TEXT DEFAULT NULL COMMENT '刷新令牌值(Base64编码)',
refresh_token_issued_at TIMESTAMP DEFAULT NULL COMMENT '刷新令牌颁发时间',
refresh_token_expires_at TIMESTAMP DEFAULT NULL COMMENT '刷新令牌过期时间',
refresh_token_metadata TEXT DEFAULT NULL COMMENT '刷新令牌元数据(JSON格式)',
-- 设备码(Device Code Flow)相关字段
user_code_value TEXT DEFAULT NULL COMMENT '用户码值(用户输入的短码)',
user_code_issued_at TIMESTAMP DEFAULT NULL COMMENT '用户码颁发时间',
user_code_expires_at TIMESTAMP DEFAULT NULL COMMENT '用户码过期时间',
user_code_metadata TEXT DEFAULT NULL COMMENT '用户码元数据(JSON格式)',
device_code_value TEXT DEFAULT NULL COMMENT '设备码值(设备生成的长码)',
device_code_issued_at TIMESTAMP DEFAULT NULL COMMENT '设备码颁发时间',
device_code_expires_at TIMESTAMP DEFAULT NULL COMMENT '设备码过期时间',
device_code_metadata TEXT DEFAULT NULL COMMENT '设备码元数据(JSON格式)',
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='OAuth 2.1 授权与令牌记录表';
2添加客户端信息
2.1SQL语句添加(不推荐)
如下所示,可以提看到,其他的一些字段,基本都是我们知道的字符串,但是其中client_settings,则很难知道填写什么。
下面是一个示例:token_settings的示例,这种配置方式是挺奇葩的,不知道那天官方开始变聪明,然后重构这的代码
{
"@class": "java.util.Collections$UnmodifiableMap",
"settings.token.reuse-refresh-tokens": true,
"settings.token.id-token-signature-algorithm": [
"org.springframework.security.oauth2.jose.jws.SignatureAlgorithm",
"RS256"
],
"settings.token.access-token-time-to-live": [
"java.time.Duration",
300
],
"settings.token.access-token-format": {
"@class": "org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat",
"value": "self-contained"
},
"settings.token.refresh-token-time-to-live": [
"java.time.Duration",
3600
],
"settings.token.authorization-code-time-to-live": [
"java.time.Duration",
300
],
"settings.token.device-code-time-to-live": [
"java.time.Duration",
300
]
}
然后我们正常的Json是下面这样的。
{
"settings": {
"settings.token.reuse-refresh-tokens": true,
"settings.token.x509-certificate-bound-access-tokens": false,
"settings.token.id-token-signature-algorithm": "RS256",
"settings.token.access-token-time-to-live": 1800,
"settings.token.access-token-format": {
"value": "self-contained"
},
"settings.token.refresh-token-time-to-live": 604800,
"settings.token.authorization-code-time-to-live": 300,
"settings.token.device-code-time-to-live": 300
},
"deviceCodeTimeToLive": 300,
"reuseRefreshTokens": true,
"x509CertificateBoundAccessTokens": false,
"refreshTokenTimeToLive": 604800,
"authorizationCodeTimeToLive": 300,
"accessTokenFormat": {
"value": "self-contained"
},
"accessTokenTimeToLive": 1800,
"idTokenSignatureAlgorithm": "RS256"
}
我尝试之下行的SQL,这个sql是不包含上述的token_settings的,最后报错:Field ‘client_settings’ doesn’t have a default value,从我们代码配置来看:这里不是简单的字符串,同理,token_settings,也是需要我们对这个client_settings、token_settings比较熟了,才可能知道,具体填写什么。
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
insert into `pcgy_gis`.`oauth2_registered_client` (`id`, `client_id`, `client_id_issued_at`, `client_secret`, `client_name`, `client_authentication_methods`, `authorization_grant_types`, `redirect_uris`, `post_logout_redirect_uris`, `scopes`)
values
('1', 'oidc-client', '2025-05-26 01:32:28', '$2a$10$.J0Rfg7y2Mu8AN8Dk2vL.eBFa9NGbOYCPOAFEw.QhgGLVXjO7eFDC', '测试客户端', 'client_secret_basic', 'authorization_code,refresh_token,client_credentials', 'http://www.baidu.com', 'http://127.0.0.1:8080/', 'openid,profile');
2.2代码添加(推荐)
比如这里,我先生成相关的代码
然后将新接口设置为不拦截:
2.2.1Json依赖
至于为什么还要单独引入jackson-datatype-jsr310,我个人觉得,在未来的某一天,说不定Spring 官方这里会被重构,毕竟,这里的代码写的确实烂(准确说Spring Oauth2的代码就写的很烂,烂到官方一直在重构代码)
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.12</version>
</dependency>
<!-- fastjson对java8日期时间进行支持 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
2.2.2保存客户端信息
注意:clientSettings.getSettings()、tokenSettings.getSettings()、@class
如果这三个参数的Json序列化不对,保存到数据库以后,进行登录认证,会报错:
java.lang.IllegalArgumentException: Could not resolve subtype of [map type; class java.util.Map, [simple type, class java.lang.String] -> [simple type, class java.lang.Object]]: missing type id property '@class'
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 268]
/**
* @description:新增OAuth 2.1 客户端注册信息表
* @author hutao
* @throws JsonProcessingException
* @date 2025-05-26
*/
@Operation(summary = "1新增OAuth 2.1 客户端注册信息表")
@PostMapping("/info/save")
@ApiOperationSupport(order = 1)
public R<String> saveOauth2RegisteredClientInfo(@RequestBody Oauth2RegisteredClient entity) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
//支持 Java8日期模块 ObjectMapper 并注册 JSR-310 模块
objectMapper.registerModule(new JavaTimeModule());
//添加@class支持
objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
//构建 clientSettings
ClientSettings clientSettings = ClientSettings.builder().requireAuthorizationConsent(true).build();
//构建 TokenSettings
TokenSettings tokenSettings = TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(30)) // 访问令牌有效期
.refreshTokenTimeToLive(Duration.ofDays(7)) // 刷新令牌有效期
.reuseRefreshTokens(true) // 允许复用刷新令牌
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)// ID令牌签名算法
.build();
entity.setClientSettings(objectMapper.writeValueAsString(clientSettings.getSettings()));
entity.setTokenSettings(objectMapper.writeValueAsString(tokenSettings.getSettings()));
oauth2RegisteredClientService.save(entity);
return R.ok();
}
2.2.3接口请求参数
{
"id": "1",
"clientId": "oidc-client",
"clientSecret": "{noop}secret",
"clientName": "客户端测试",
"clientAuthenticationMethods": "client_secret_basic",
"authorizationGrantTypes": "authorization_code,refresh_token,client_credentials",
"redirectUris": "http://www.baidu.com",
"postLogoutRedirectUris": "http://127.0.0.1:8080/",
"scopes": "openid,profile",
"clientSettings": "",
"tokenSettings": ""
}
如果你的Json报错:是因为 没有对Jackson 对 Java 8 日期时间的支持
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.Duration` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
2.2.4测试验证
通过调用接口,clientSettings、和tokenSettings写入数据库
3修改默认配置
3.1删除内存存储信息代码
3.2注入 RegisteredClientRepository
/*
* @Description: 客户端信息:对应表:oauth2_registered_client
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月26日 上午10:50:52
*/
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
3.3注入OAuth2AuthorizationService
/*
* @Description: 授权信息:对应表:oauth2_authorization
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月26日 上午10:51:10
*/
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
3.4注入OAuth2AuthorizationConsentService
/*
* @Description: 授权确认:对应表:oauth2_authorization_consent
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月26日 上午10:51:29
*/
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
4验证认证
4.1客户端认证验证
4.2授权码认证验证
http://localhost:18080/oauth2/authorize?response_type=code&client_id=oidc-client&scope=profile
4.3数据库操作验证
查看oauth2-authorization、oauth2-authorization-consen表
标题secret的默认加密
我们新增的客户端信息如下:
“clientSecret”: “{noop}secret”
仅新增时,数据库是这样的。
但是当我们进行任意的登录授权以后,此时在查看数据库中存储的信息,已经变成加密了
5完整代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.web.context.WebApplicationContext;
import com.example.demo.UrlsUtils;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.extern.log4j.Log4j2;
@Configuration
@EnableWebSecurity
@Log4j2
public class SecurityConfig {
@Autowired
private WebApplicationContext applicationContext;
@Value(value = "${ignore-url}")
private String ignoreUrl;
@Value(value = "${package.path}")
private String packagePath;
/*
* @Description: 配置授权服务器(用于登录操作)
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月23日 下午3:15:35
*/
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.oidc(Customizer.withDefaults())
)
.authorizeHttpRequests((authorize) ->
authorize
.anyRequest().authenticated()
)
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
return http.build();
}
/*
* @Description: 配置需要保护的资源
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月23日 下午2:28:20
*/
@Bean
@Order(2)
public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception {
//获取所有的接口地址
Map<String, Object> filteredControllers = UrlsUtils.getControllerClass(packagePath, applicationContext);
// 使用过滤后的控制器获取 URL
List<String> allUrl = UrlsUtils.getControllerUrls(filteredControllers);
String[] split = ignoreUrl.split(";");
for (String url : split) {
url = url.replaceAll(" ", "");
UrlsUtils.removeUrl(allUrl, url);
UrlsUtils.removeUrl(allUrl, url);
}
log.info("受保护的API地址:{}",allUrl);
http
.securityMatcher(allUrl.toArray(new String[0]))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.formLogin(form -> form.disable());
return http.build();
}
/*
* @Description: 配置不需要保护的资源
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月23日 下午2:28:20
*/
@Bean
@Order(3)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
String[] split = ignoreUrl.split(";");
for (int i = 0; i < split.length; i++) {
split[i] = split[i].replaceAll(" ", "");
}
log.info("公共开放的API地址:{}",Arrays.asList(split));
http
.csrf(csrf -> csrf.ignoringRequestMatchers(split))
.authorizeHttpRequests(authorize ->
authorize
.requestMatchers(split).permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("hutao")
.password("2025.com")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
/* @Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("oidc-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://www.baidu.com")
.postLogoutRedirectUri("http://127.0.0.1:8080/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(oidcClient);
}*/
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
/*
* @Description: 客户端信息:对应表:oauth2_registered_client
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月26日 上午10:50:52
*/
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
/*
* @Description: 授权信息:对应表:oauth2_authorization
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月26日 上午10:51:10
*/
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
/*
* @Description: 授权确认:对应表:oauth2_authorization_consent
* @author: 胡涛
* @mail: hutao_2017@aliyun.com
* @date: 2025年5月26日 上午10:51:29
*/
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
}

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)