基于Spring Security 6的OAuth2 系列之七 - 授权服务器--自定义数据库客户端信息

news2025/2/5 18:57:41

之所以想写这一系列,是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级为6.3.0。无论是Spring Security的风格和以及OAuth2都做了较大改动,里面甚至将授权服务器模块都移除了,导致在配置同样功能时,花费了些时间研究新版本的底层原理,这里将一些学习经验分享给大家。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 spring-boo-3.3.0(默认引入的Spring Security是6.3.0),JDK版本使用的是19,本系列OAuth2的代码采用Spring Security6.3.0框架,所有代码都在oauth2-study项目上:https://github.com/forever1986/oauth2-study.git

目录

  • 1 客户端认证原理
  • 2 Spring Authrization Server客户端表说明
  • 3 基于数据库客户端
    • 3.1 自带的Jdbc实现类
    • 3.2 使用自带的Jdbc实现
  • 4 基于自定义数据库客户端

前面我们自定义了授权页面,但是截止到目前为止,我们的客户端应用注册都是放在yaml文件或者在代码中加入,其实就是基于内存存储中,这样会导致每次增加客户端都要重启。在实际项目中,一般会放在数据库或者Redis缓存中,本章就将实现基于数据库的客户端应用注册。在了解如何自定义基于数据库的客户端之前,我们先来了解一下客户端认证的原理

1 客户端认证原理

我们知道Spring Authrization Server虽然从Spring Security分离出来,但是底层还是基于Spring Security的,如果读过Spring Security 6系列之二的朋友,应该很快就能掌握这一部分,因为实现的方式几乎一样。

1)看源码就是直接看过滤器,我们先看看OAuth2AuthorizationEndpointFilter,其doFilterInternal方法中就做了认证
在这里插入图片描述

2)从上图可以知道基于AuthenticationManager,而AuthenticationManager只是一个接口,实际的实现类ProviderManager。但是其实ProviderManager只是一个代理。ProviderManager里面有一个AuthenticationProvider数组,通过这个数据实现不同认证的。这部分都是Spring Security的内容
在这里插入图片描述

3)客户端信息是通过OAuth2AuthorizationCodeRequestAuthenticationProvider实现类的

在这里插入图片描述

4)到此,我们就知道获取客户端信息就是使用RegisteredClientRepository,我们再看看RegisteredClientRepository,返回的是客户端信息放在一个RegisteredClient类,另外RegisteredClientRepository有两个实现类

  • InMemoryRegisteredClientRepository:基于内存,我们在yaml文件中配置都是基于内存,这个系列一中Spring Boot自动化配置可以找到注入原理
  • JdbcRegisteredClientRepository:基于数据库,可以看到该类是基于传统的Jdbc方式实现

在这里插入图片描述

从原理分析,我们知道要么我们直接使用JdbcRegisteredClientRepository,要么就自定义一个RegisteredClientRepository。下面,我们先了解相关的数据库表。

2 Spring Authrization Server客户端表说明

既然要保存到数据库,那么就需要做数据库表,Spring Authrization Server已经为我们准备好了SQL,但不是一张表,而是3张表。如下图Spring Authrization Server有三张跟OAuth2流程有关的表:分别是oauth2_registered_client(客户端表)、oauth2_authorization(授权表)、oauth2_authorization_consent (授权确认表),下面说一下3张表作用和流程

  • 1)首先是你需要将授权服务器中原先在yaml文件中配置的客户端信息存入oauth2_registered_client(客户端表)
  • 2)当你进入授权页面时,授权服务器会往oauth2_authorization(授权表)中插入一条授权信息
  • 3)当你确认授权之后,授权服务器会更新oauth2_authorization(授权表)的信息,同时往oauth2_authorization_consent (授权确认表)插入一条关联oauth2_registered_client表和oauth2_authorization表的记录,这样下次就不用再次授权

在这里插入图片描述

  • 客户端信息表:保存客户端信息的,可以参考一下注解大概知道其字段含义
CREATE TABLE oauth2_registered_client (
	-- 唯一标识id
    id varchar(100) NOT NULL,
    -- 注册客户端id
    client_id varchar(100) NOT NULL,
    -- 注册客户端签发时间(默认是当前时间)
    client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
    -- 注册客户端密钥
    client_secret varchar(200) DEFAULT NULL,
    -- 注册客户端过期时间
    client_secret_expires_at timestamp DEFAULT NULL,
    -- 注册客户端名称
    client_name varchar(200) NOT NULL,
    -- 注册客户端验证方法
    client_authentication_methods varchar(1000) NOT NULL,
    -- 注册客户端授权模式:授权码模式、客户端模式等
    authorization_grant_types varchar(1000) NOT NULL,
    -- 注册客户端回调地址
    redirect_uris varchar(1000) DEFAULT NULL,
    -- 注册客户端首页
    post_logout_redirect_uris varchar(1000) DEFAULT NULL,
    -- 注册客户端授权范围
    scopes varchar(1000) NOT NULL,
    -- 注册客户端配置
    client_settings varchar(2000) NOT NULL,
    -- token配置
    token_settings varchar(2000) NOT NULL,
    PRIMARY KEY (id)
);
  • 授权信息表:授权信息,在跳转到授权页面时,会插入一条信息。
/*
IMPORTANT:
    If using PostgreSQL, update ALL columns defined with 'blob' to 'text',
    as PostgreSQL does not support the 'blob' data type.
*/
CREATE TABLE oauth2_authorization (
    id varchar(100) NOT NULL,
    registered_client_id varchar(100) NOT NULL,
    principal_name varchar(200) NOT NULL,
    authorization_grant_type varchar(100) NOT NULL,
    authorized_scopes varchar(1000) DEFAULT NULL,
    attributes blob DEFAULT NULL,
    state varchar(500) DEFAULT NULL,
    authorization_code_value blob DEFAULT NULL,
    authorization_code_issued_at timestamp DEFAULT NULL,
    authorization_code_expires_at timestamp DEFAULT NULL,
    authorization_code_metadata blob DEFAULT NULL,
    access_token_value blob DEFAULT NULL,
    access_token_issued_at timestamp DEFAULT NULL,
    access_token_expires_at timestamp DEFAULT NULL,
    access_token_metadata blob DEFAULT NULL,
    access_token_type varchar(100) DEFAULT NULL,
    access_token_scopes varchar(1000) DEFAULT NULL,
    oidc_id_token_value blob DEFAULT NULL,
    oidc_id_token_issued_at timestamp DEFAULT NULL,
    oidc_id_token_expires_at timestamp DEFAULT NULL,
    oidc_id_token_metadata blob DEFAULT NULL,
    refresh_token_value blob DEFAULT NULL,
    refresh_token_issued_at timestamp DEFAULT NULL,
    refresh_token_expires_at timestamp DEFAULT NULL,
    refresh_token_metadata blob DEFAULT NULL,
    user_code_value blob DEFAULT NULL,
    user_code_issued_at timestamp DEFAULT NULL,
    user_code_expires_at timestamp DEFAULT NULL,
    user_code_metadata blob DEFAULT NULL,
    device_code_value blob DEFAULT NULL,
    device_code_issued_at timestamp DEFAULT NULL,
    device_code_expires_at timestamp DEFAULT NULL,
    device_code_metadata blob DEFAULT NULL,
    PRIMARY KEY (id)
);

  • 确认授权表:授权确认后,就会将客户端表的记录与授权信息表的记录关联在一起,并记录授权情况
CREATE TABLE oauth2_authorization_consent (
    registered_client_id varchar(100) NOT NULL,
    principal_name varchar(200) NOT NULL,
    authorities varchar(1000) NOT NULL,
    PRIMARY KEY (registered_client_id, principal_name)
);

这3个信息都有内存和数据库实现方式,默认都是内存方式。

3 基于数据库客户端

我们先展现以自带实现Jdbc的类的实现方式,后面实现完全自定义的方式。

3.1 自带的Jdbc实现类

从源码中,我们知道其读取的接口分别是RegisteredClientRepositoryOAuth2AuthorizationServiceOAuth2AuthorizationConsentService。而这几个接口分别都有内存实现和数据库实现的类,如下图以RegisteredClientRepository为例,就可以看到有这2个实现类

在这里插入图片描述

3.2 使用自带的Jdbc实现

1)既然Spring Security已经有其实现类,那么我们实现数据库存储只需要将默认内存换成Jdbc方式,只需要在SecurityConfig注入对应的Bean

@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate){
    return new JdbcRegisteredClientRepository(jdbcTemplate);
}

@Bean
public OAuth2AuthorizationService oAuth2AuthorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository){
    return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}

@Bean
public OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository){
    return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}

2)需要创建对应的表,并在yaml文件中配置数据库连接即可

其默认数据库存储都是基于传统的Jdbc方式进行的,但是很多生产项目其实都会使用Mybatis等框架,下面就基于mybatis-plus重新定义这几个Jdbc。

4 基于自定义数据库客户端

代码参考lesson04子模块,该模块是一个自定义数据库客户端的授权服务器,这一章还会利用lesson02子模块的oauth-client模块作为客户端演示

lesson04 前提条件:本次演示我们先在mysql数据库创建oauth-study库,并创建表oauth2_registered_client、oauth2_authorization和oauth2_authorization_consent三个表

1)在mysql数据库创建oauth-study库,并创建表oauth2_registered_client、oauth2_authorization和oauth2_authorization_consent三个表

2)新建lesson04子模块,其pom引入如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-authorization-server</artifactId>
    </dependency>
    <!-- lombok依赖,用于get/set的简便-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- mysql依赖,用于连接mysql数据库-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- mybatis-plus依赖,用于使用mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    </dependency>
    <!-- pool2和druid依赖,用于mysql连接池-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <!-- 解决java.time.Duration序列化问题-->
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>
    <!-- 解决jacketjson序列化包 -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-cas</artifactId>
    </dependency>
</dependencies>

3)在entity包下自定义类SelfRegisteredClient、SelfOAuth2Authorization和SelfOAuth2AuthorizationConsent三个类,分别对应数据库表,之所以无法使用RegisteredClient、OAuth2Authorization和OAuth2AuthorizationConsent,是因为这些类的属性并不与数据库字段一一对应,同时有些字段序列化到数据库需要特殊处理,因此需要自定义。(注意:里面有些字段需要使用特殊TypeHandler处理,在后面会附上这些特殊定义的TypeHandler)

@TableName("oauth2_registered_client")
@Data
public class SelfRegisteredClient implements Serializable {

    private String id;

    private String clientId;

    private Instant clientIdIssuedAt;

    private String clientSecret;

    private Instant clientSecretExpiresAt;

    private String clientName;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> clientAuthenticationMethods;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> authorizationGrantTypes;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> redirectUris;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> postLogoutRedirectUris;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> scopes;

    @TableField(typeHandler = ClientSettingsTypeHandler.class)
    private ClientSettings clientSettings;

    @TableField(typeHandler = TokenSettingsTypeHandler.class)
    private TokenSettings tokenSettings;

    public static RegisteredClient covertRegisteredClient(SelfRegisteredClient selfClient){
        if(selfClient!=null){
            return RegisteredClient
                    .withId(selfClient.getId())
                    .clientId(selfClient.getClientId())
                    .clientSecret(selfClient.getClientSecret())
                    .clientName(selfClient.getClientName())
                    .clientIdIssuedAt(selfClient.getClientIdIssuedAt())
                    .clientSecretExpiresAt(selfClient.getClientSecretExpiresAt())
                    .clientAuthenticationMethods(methods->{
                        methods.addAll(SelfRegisteredClient.getMethodSetFromString(selfClient.getClientAuthenticationMethods()));
                    })
                    .authorizationGrantTypes(types->{
                        types.addAll(SelfRegisteredClient.getSetTypeFromString(selfClient.getAuthorizationGrantTypes()));
                    })
                    .redirectUris(uris->{
                        uris.addAll(selfClient.getRedirectUris());
                    })
                    .postLogoutRedirectUris(uris->{
                        uris.addAll(selfClient.getPostLogoutRedirectUris());
                    })
                    .scopes(scopes1 ->{
                        scopes1.addAll(selfClient.getScopes());
                    })
                    .tokenSettings(selfClient.getTokenSettings())
                    .clientSettings(selfClient.getClientSettings())
                    .build();
        }
        return null;
    }

    public static SelfRegisteredClient covertSelfRegisteredClient(RegisteredClient client){
        if(client!=null){
            SelfRegisteredClient selfRegisteredClient = new SelfRegisteredClient();
            selfRegisteredClient.setId(client.getId());
            selfRegisteredClient.setClientId(client.getClientId());
            selfRegisteredClient.setClientSecret(client.getClientSecret());
            selfRegisteredClient.setClientName(client.getClientName());
            selfRegisteredClient.setClientAuthenticationMethods(getSetFromMethod(client.getClientAuthenticationMethods()));
            selfRegisteredClient.setAuthorizationGrantTypes(getSetFromType(client.getAuthorizationGrantTypes()));
            selfRegisteredClient.setRedirectUris(client.getRedirectUris());
            selfRegisteredClient.setPostLogoutRedirectUris(client.getPostLogoutRedirectUris());
            selfRegisteredClient.setScopes(client.getScopes());
            selfRegisteredClient.setClientSettings(client.getClientSettings());
            selfRegisteredClient.setTokenSettings(client.getTokenSettings());
            selfRegisteredClient.setClientIdIssuedAt(client.getClientIdIssuedAt());
            selfRegisteredClient.setClientSecretExpiresAt(client.getClientSecretExpiresAt());
            return selfRegisteredClient;
        }
        return null;
    }

    public static Set<AuthorizationGrantType> getSetTypeFromString(Set<String> strs){
        Set<AuthorizationGrantType> set = new HashSet<>();
        if(strs!=null&& !strs.isEmpty()){
            // 这里只是用目前OAuth2.1支持的类型,原先的密码就不支持
            for(String authorizationGrantType : strs){
                AuthorizationGrantType type;
                if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
                    type = AuthorizationGrantType.AUTHORIZATION_CODE;
                }
                else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
                    type = AuthorizationGrantType.CLIENT_CREDENTIALS;
                }
                else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
                    type = AuthorizationGrantType.REFRESH_TOKEN;
                }else{
                    // Custom authorization grant type
                    type = new AuthorizationGrantType(authorizationGrantType);
                }
                set.add(type);
            }
        }
        return set;
    }

    public static Set<ClientAuthenticationMethod> getMethodSetFromString(Set<String> strs){
        Set<ClientAuthenticationMethod> set = new HashSet<>();
        if(strs!=null&& !strs.isEmpty()){
            for(String method : strs){
                ClientAuthenticationMethod clientAuthenticationMethod;
                if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(method)) {
                    clientAuthenticationMethod = ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
                }
                else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(method)) {
                    clientAuthenticationMethod = ClientAuthenticationMethod.CLIENT_SECRET_POST;
                }
                else if (ClientAuthenticationMethod.NONE.getValue().equals(method)) {
                    clientAuthenticationMethod = ClientAuthenticationMethod.NONE;
                }else {
                    // Custom client authentication method
                    clientAuthenticationMethod = new ClientAuthenticationMethod(method);
                }
                set.add(clientAuthenticationMethod);
            }
        }
        return set;
    }

    public static Set<String> getSetFromType(Set<AuthorizationGrantType> parameters){
        Set<String> set = new HashSet<>();
        if(parameters!=null){
            StringBuilder sb = new StringBuilder();
            for(AuthorizationGrantType parameter : parameters){
                set.add(parameter.getValue());
            }
        }
        return set;
    }

    public static Set<String> getSetFromMethod(Set<ClientAuthenticationMethod> parameters){
        Set<String> set = new HashSet<>();
        if(parameters!=null){
            StringBuilder sb = new StringBuilder();
            for(ClientAuthenticationMethod parameter : parameters){
                set.add(parameter.getValue());
            }
        }
        return set;
    }

}
@TableName("oauth2_authorization")
@Data
public class SelfOAuth2Authorization implements Serializable {

    private String id;

    private String registeredClientId;

    private String principalName;

    private String authorizationGrantType;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> authorizedScopes;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> attributes;

    private String state;

    private String authorizationCodeValue;

    private Timestamp authorizationCodeIssuedAt;

    private Timestamp authorizationCodeExpiresAt;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> authorizationCodeMetadata;

    private String accessTokenValue;

    private Timestamp accessTokenIssuedAt;

    private Timestamp accessTokenExpiresAt;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> accessTokenMetadata;

    private String  accessTokenType;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String>  accessTokenScopes;

    private String oidcIdTokenValue;

    private Timestamp oidcIdTokenIssuedAt;

    private Timestamp oidcIdTokenExpiresAt;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> oidcIdTokenMetadata;

    private String refreshTokenValue;

    private Timestamp refreshTokenIssuedAt;

    private Timestamp refreshTokenExpiresAt;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> refreshTokenMetadata;

    private String userCodeValue;

    private Timestamp userCodeIssuedAt;

    private Timestamp userCodeExpiresAt;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> userCodeMetadata;

    private String deviceCodeValue;

    private Timestamp deviceCodeIssuedAt;

    private Timestamp deviceCodeExpiresAt;

    @TableField(typeHandler = TokenMetadataTypeHandler.class)
    private Map<String, Object> deviceCodeMetadata;


    public static OAuth2Authorization covertOAuth2Authorization(SelfOAuth2Authorization selfOAuth2Authorization, RegisteredClientRepository registeredClientRepository){
        if(selfOAuth2Authorization!=null){
            RegisteredClient registeredClient = registeredClientRepository.findById(selfOAuth2Authorization.getRegisteredClientId());
            if (registeredClient == null) {
                throw new DataRetrievalFailureException("The RegisteredClient with id '" + selfOAuth2Authorization.getRegisteredClientId()
                        + "' was not found in the RegisteredClientRepository.");
            }

            OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient);
            builder.id(selfOAuth2Authorization.getId())
                    .principalName(selfOAuth2Authorization.getPrincipalName())
                    .authorizationGrantType(new AuthorizationGrantType(selfOAuth2Authorization.getAuthorizationGrantType()))
                    .authorizedScopes(selfOAuth2Authorization.getAuthorizedScopes())
                    .attributes((attrs) -> attrs.putAll(selfOAuth2Authorization.getAttributes()));

            String state = selfOAuth2Authorization.getState();
            if (StringUtils.hasText(state)) {
                builder.attribute(OAuth2ParameterNames.STATE, state);
            }

            Instant tokenIssuedAt;
            Instant tokenExpiresAt;
            String authorizationCodeValue = selfOAuth2Authorization.getAuthorizationCodeValue();

            if (StringUtils.hasText(authorizationCodeValue)) {
                tokenIssuedAt = selfOAuth2Authorization.getAuthorizationCodeIssuedAt().toInstant();
                tokenExpiresAt = selfOAuth2Authorization.getAuthorizationCodeExpiresAt().toInstant();
                Map<String, Object> authorizationCodeMetadata = selfOAuth2Authorization.getAuthorizationCodeMetadata();

                OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(authorizationCodeValue,
                        tokenIssuedAt, tokenExpiresAt);
                builder.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata));
            }

            String accessTokenValue = selfOAuth2Authorization.getAccessTokenValue();
            if (StringUtils.hasText(accessTokenValue)) {
                tokenIssuedAt = selfOAuth2Authorization.getAccessTokenIssuedAt().toInstant();
                tokenExpiresAt = selfOAuth2Authorization.getAccessTokenExpiresAt().toInstant();
                Map<String, Object> accessTokenMetadata = selfOAuth2Authorization.getAccessTokenMetadata();
                OAuth2AccessToken.TokenType tokenType = null;
                if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(selfOAuth2Authorization.getAccessTokenType())) {
                    tokenType = OAuth2AccessToken.TokenType.BEARER;
                }

                Set<String> scopes = selfOAuth2Authorization.getAccessTokenScopes();
                OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, accessTokenValue, tokenIssuedAt,
                        tokenExpiresAt, scopes);
                builder.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata));
            }

            String oidcIdTokenValue = selfOAuth2Authorization.getOidcIdTokenValue();
            if (StringUtils.hasText(oidcIdTokenValue)) {
                tokenIssuedAt = selfOAuth2Authorization.getOidcIdTokenIssuedAt().toInstant();
                tokenExpiresAt = selfOAuth2Authorization.getOidcIdTokenExpiresAt().toInstant();
                Map<String, Object> oidcTokenMetadata = selfOAuth2Authorization.getOidcIdTokenMetadata();

                OidcIdToken oidcToken = new OidcIdToken(oidcIdTokenValue, tokenIssuedAt, tokenExpiresAt,
                        (Map<String, Object>) oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME));
                builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata));
            }

            String refreshTokenValue = selfOAuth2Authorization.getRefreshTokenValue();
            if (StringUtils.hasText(refreshTokenValue)) {
                tokenIssuedAt = selfOAuth2Authorization.getRefreshTokenIssuedAt().toInstant();
                tokenExpiresAt = null;
                Timestamp refreshTokenExpiresAt = selfOAuth2Authorization.getRefreshTokenExpiresAt();
                if (refreshTokenExpiresAt != null) {
                    tokenExpiresAt = refreshTokenExpiresAt.toInstant();
                }
                Map<String, Object> refreshTokenMetadata = selfOAuth2Authorization.getRefreshTokenMetadata();

                OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(refreshTokenValue, tokenIssuedAt,
                        tokenExpiresAt);
                builder.token(refreshToken, (metadata) -> metadata.putAll(refreshTokenMetadata));
            }

            String userCodeValue = selfOAuth2Authorization.getUserCodeValue();
            if (StringUtils.hasText(userCodeValue)) {
                tokenIssuedAt = selfOAuth2Authorization.getUserCodeIssuedAt().toInstant();
                tokenExpiresAt = selfOAuth2Authorization.getUserCodeExpiresAt().toInstant();
                Map<String, Object> userCodeMetadata = selfOAuth2Authorization.getUserCodeMetadata();

                OAuth2UserCode userCode = new OAuth2UserCode(userCodeValue, tokenIssuedAt, tokenExpiresAt);
                builder.token(userCode, (metadata) -> metadata.putAll(userCodeMetadata));
            }

            String deviceCodeValue = selfOAuth2Authorization.getDeviceCodeValue();
            if (StringUtils.hasText(deviceCodeValue)) {
                tokenIssuedAt = selfOAuth2Authorization.getDeviceCodeIssuedAt().toInstant();
                tokenExpiresAt = selfOAuth2Authorization.getDeviceCodeExpiresAt().toInstant();
                Map<String, Object> deviceCodeMetadata = selfOAuth2Authorization.getDeviceCodeMetadata();

                OAuth2DeviceCode deviceCode = new OAuth2DeviceCode(deviceCodeValue, tokenIssuedAt, tokenExpiresAt);
                builder.token(deviceCode, (metadata) -> metadata.putAll(deviceCodeMetadata));
            }
            return builder.build();
        }
        return null;
    }

    public static SelfOAuth2Authorization covertSelfOAuth2Authorization(OAuth2Authorization auth2Authorization){
        if(auth2Authorization!=null){

            SelfOAuth2Authorization selfOAuth2Authorization = new SelfOAuth2Authorization();
            selfOAuth2Authorization.setId(auth2Authorization.getId());
            selfOAuth2Authorization.setRegisteredClientId(auth2Authorization.getRegisteredClientId());
            selfOAuth2Authorization.setPrincipalName(auth2Authorization.getPrincipalName());
            selfOAuth2Authorization.setAuthorizationGrantType(auth2Authorization.getAuthorizationGrantType().getValue());

            selfOAuth2Authorization.setAuthorizedScopes(auth2Authorization.getAuthorizedScopes());

            selfOAuth2Authorization.setAttributes(auth2Authorization.getAttributes());

            String state = null;
            String authorizationState = auth2Authorization.getAttribute(OAuth2ParameterNames.STATE);
            if (StringUtils.hasText(authorizationState)) {
                state = authorizationState;
            }
            selfOAuth2Authorization.setState(state==null?"":state);

            OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = auth2Authorization
                    .getToken(OAuth2AuthorizationCode.class);
            if(authorizationCode!=null){
                selfOAuth2Authorization.setAuthorizationCodeValue(authorizationCode.getToken().getTokenValue());
                selfOAuth2Authorization.setAuthorizationCodeIssuedAt(new Timestamp(authorizationCode.getToken().getIssuedAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setAuthorizationCodeExpiresAt(new Timestamp(authorizationCode.getToken().getExpiresAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setAuthorizationCodeMetadata(authorizationCode.getMetadata());
            }

            OAuth2Authorization.Token<OAuth2AccessToken> accessToken = auth2Authorization.getToken(OAuth2AccessToken.class);
            if (accessToken != null) {
                selfOAuth2Authorization.setAccessTokenValue(accessToken.getToken().getTokenValue());
                selfOAuth2Authorization.setAccessTokenIssuedAt(new Timestamp(accessToken.getToken().getIssuedAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setAccessTokenExpiresAt(new Timestamp(accessToken.getToken().getExpiresAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setAccessTokenMetadata(accessToken.getMetadata());
                selfOAuth2Authorization.setAccessTokenType(accessToken.getToken().getTokenType().getValue());
                selfOAuth2Authorization.setAccessTokenScopes(accessToken.getToken().getScopes());
            }

            OAuth2Authorization.Token<OidcIdToken> oidcIdToken = auth2Authorization.getToken(OidcIdToken.class);
            if (oidcIdToken != null) {
                selfOAuth2Authorization.setOidcIdTokenValue(oidcIdToken.getToken().getTokenValue());
                selfOAuth2Authorization.setOidcIdTokenIssuedAt(new Timestamp(oidcIdToken.getToken().getIssuedAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setOidcIdTokenExpiresAt(new Timestamp(oidcIdToken.getToken().getExpiresAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setOidcIdTokenMetadata(oidcIdToken.getMetadata());
            }

            OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = auth2Authorization.getRefreshToken();
            if (refreshToken != null) {
                selfOAuth2Authorization.setRefreshTokenValue(refreshToken.getToken().getTokenValue());
                selfOAuth2Authorization.setRefreshTokenIssuedAt(new Timestamp(refreshToken.getToken().getIssuedAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setRefreshTokenExpiresAt(new Timestamp(refreshToken.getToken().getExpiresAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setRefreshTokenMetadata(refreshToken.getMetadata());
            }

            OAuth2Authorization.Token<OAuth2UserCode> userCode = auth2Authorization.getToken(OAuth2UserCode.class);
            if (userCode != null) {
                selfOAuth2Authorization.setUserCodeValue(userCode.getToken().getTokenValue());
                selfOAuth2Authorization.setUserCodeIssuedAt(new Timestamp(userCode.getToken().getIssuedAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setUserCodeExpiresAt(new Timestamp(userCode.getToken().getExpiresAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setUserCodeMetadata(userCode.getMetadata());
            }

            OAuth2Authorization.Token<OAuth2DeviceCode> deviceCode = auth2Authorization.getToken(OAuth2DeviceCode.class);
            if (deviceCode != null) {
                selfOAuth2Authorization.setDeviceCodeValue(deviceCode.getToken().getTokenValue());
                selfOAuth2Authorization.setDeviceCodeIssuedAt(new Timestamp(deviceCode.getToken().getIssuedAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setDeviceCodeExpiresAt(new Timestamp(deviceCode.getToken().getExpiresAt().getEpochSecond()*1000));
                selfOAuth2Authorization.setDeviceCodeMetadata(deviceCode.getMetadata());
            }

            return selfOAuth2Authorization;
        }
        return null;
    }
}
@TableName("oauth2_authorization_consent")
@Data
public class SelfOAuth2AuthorizationConsent implements Serializable {

    private String registeredClientId;

    private String principalName;

    @TableField(typeHandler = SetStringTypeHandler.class)
    private Set<String> authorities;


    public static SelfOAuth2AuthorizationConsent convertSelfOAuth2AuthorizationConsent(OAuth2AuthorizationConsent auth2AuthorizationConsent){
        if(auth2AuthorizationConsent!=null){
            SelfOAuth2AuthorizationConsent selfOAuth2AuthorizationConsent = new SelfOAuth2AuthorizationConsent();
            selfOAuth2AuthorizationConsent.setRegisteredClientId(auth2AuthorizationConsent.getRegisteredClientId());
            selfOAuth2AuthorizationConsent.setPrincipalName(auth2AuthorizationConsent.getPrincipalName());
            if(auth2AuthorizationConsent.getAuthorities()!=null){
                selfOAuth2AuthorizationConsent.setAuthorities(auth2AuthorizationConsent.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()));
            }
            return selfOAuth2AuthorizationConsent;
        }
        return null;
    }

    public static OAuth2AuthorizationConsent convertOAuth2AuthorizationConsent(SelfOAuth2AuthorizationConsent selfOAuth2AuthorizationConsent, RegisteredClientRepository registeredClientRepository){
        if(selfOAuth2AuthorizationConsent!=null){
            RegisteredClient registeredClient = registeredClientRepository.findById(selfOAuth2AuthorizationConsent.getRegisteredClientId());
            if (registeredClient == null) {
                throw new DataRetrievalFailureException("The RegisteredClient with id '" + selfOAuth2AuthorizationConsent.getRegisteredClientId()
                        + "' was not found in the RegisteredClientRepository.");
            }
            OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(selfOAuth2AuthorizationConsent.getRegisteredClientId(),
                    selfOAuth2AuthorizationConsent.getPrincipalName());
            for (String authority : selfOAuth2AuthorizationConsent.getAuthorities()) {
                builder.authority(new SimpleGrantedAuthority(authority));
            }
            return builder.build();
        }
        return null;
    }
}

4)在mapper包下,自定义Mapper读取oauth2_registered_client 表

@Mapper
public interface Oauth2RegisteredClientMapper extends BaseMapper<SelfRegisteredClient> {

    // 根据client_id,查询客户端信息
    @Select("select * from oauth2_registered_client where client_id = #{client_id}")
    SelfRegisteredClient selectByClientId(String client_id);
}
@Mapper
public interface OAuth2AuthorizationMapper extends BaseMapper<SelfOAuth2Authorization> {
}
@Mapper
public interface OAuth2AuthorizationConsentMapper extends BaseMapper<SelfOAuth2AuthorizationConsent> {
}

5)在handler包下,自定义某些字段存储到库的TypeHandler。这是由于三个表中有几个字段需要特殊存储,因此需要自定义TypeHandler

@MappedJdbcTypes({JdbcType.VARCHAR})  //对应数据库类型
@MappedTypes({ClientSettings.class})            //java数据类型
public class ClientSettingsTypeHandler implements TypeHandler<ClientSettings> {


    private ObjectMapper objectMapper;

    public ClientSettingsTypeHandler() {
        objectMapper = new ObjectMapper();
        /**
         * 此处注册json存储格式化
         */
        ClassLoader classLoader = ClientSettingsTypeHandler.class.getClassLoader();
        List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
        this.objectMapper.registerModules(securityModules);
        this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, ClientSettings parameter, JdbcType jdbcType) throws SQLException {
        if(parameter!=null&&parameter.getSettings()!=null){
            ps.setString(i ,writeMap(parameter.getSettings()));
        }else{
            ps.setString(i, "");
        }

    }

    @Override
    public ClientSettings getResult(ResultSet rs, String columnName) throws SQLException {
        String str = rs.getString(columnName);
        return ClientSettings.withSettings(parseMap(str)).build();
    }

    @Override
    public ClientSettings getResult(ResultSet rs, int columnIndex) throws SQLException {
        String str = rs.getString(columnIndex);
        return ClientSettings.withSettings(parseMap(str)).build();
    }

    @Override
    public ClientSettings getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String str = cs.getString(columnIndex);
        return ClientSettings.withSettings(parseMap(str)).build();
    }

    private String writeMap(Map<String, Object> data) {
        try {
            return this.objectMapper.writeValueAsString(data);
        }
        catch (Exception ex) {
            throw new IllegalArgumentException(ex.getMessage(), ex);
        }
    }

    private Map<String, Object> parseMap(String data) {
        if(data!=null&&!data.isEmpty()){
            try {
                return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
                });
            }
            catch (Exception ex) {
                throw new IllegalArgumentException(ex.getMessage(), ex);
            }
        }else{
            return new HashMap<>();
        }
    }
}
@MappedJdbcTypes({JdbcType.VARCHAR})  //对应数据库类型
@MappedTypes({Set.class})            //java数据类型
public class SetStringTypeHandler implements TypeHandler<Set<String>> {

    private static final String COMMA =",";

    @Override
    public void setParameter(PreparedStatement ps, int i, Set<String> parameters, JdbcType jdbcType) throws SQLException {
        String str = "";
        if(parameters!=null){
            str = String.join(COMMA, parameters);
        }
        ps.setString(i, str);
    }

    @Override
    public Set<String> getResult(ResultSet rs, String columnName) throws SQLException {
        String str = rs.getString(columnName);
        return getSetFromString(str);
    }

    @Override
    public Set<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
        String str = rs.getString(columnIndex);
        return getSetFromString(str);
    }

    @Override
    public Set<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String str = cs.getString(columnIndex);
        return getSetFromString(str);
    }

    private Set<String> getSetFromString(String str){
        Set<String> set = new HashSet<>();
        if(str!=null&& !str.isEmpty()){
            String[] strs = str.split(COMMA);
            Collections.addAll(set, strs);
        }
        return set;
    }
}
@MappedJdbcTypes({JdbcType.BLOB})  //对应数据库类型
@MappedTypes({Map.class})
public class TokenMetadataTypeHandler implements TypeHandler<Map<String, Object>> {


    private ObjectMapper objectMapper;

    public TokenMetadataTypeHandler() {
        objectMapper = new ObjectMapper();
        /**
         * 此处注册json存储格式化
         */
        ClassLoader classLoader = TokenMetadataTypeHandler.class.getClassLoader();
        List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
        this.objectMapper.registerModules(securityModules);
        this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) throws SQLException {
        if(parameter!=null){
            ps.setString(i ,writeMap(parameter));
        }else{
            ps.setString(i, "");
        }
    }

    @Override
    public Map<String, Object> getResult(ResultSet rs, String columnName) throws SQLException {
        String str = rs.getString(columnName);
        return parseMap(str);
    }

    @Override
    public Map<String, Object> getResult(ResultSet rs, int columnIndex) throws SQLException {
        String str = rs.getString(columnIndex);
        return parseMap(str);
    }

    @Override
    public Map<String, Object> getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String str = cs.getString(columnIndex);
        return parseMap(str);
    }


    private String writeMap(Map<String, Object> data) {
        try {
            this.objectMapper.findAndRegisterModules();
            return this.objectMapper.writeValueAsString(data);
        }
        catch (Exception ex) {
            throw new IllegalArgumentException(ex.getMessage(), ex);
        }
    }

    private Map<String, Object> parseMap(String data) {
        if(data!=null&&!data.isEmpty()){
            try {
                return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
                });
            }
            catch (Exception ex) {
                throw new IllegalArgumentException(ex.getMessage(), ex);
            }
        }else{
            return new HashMap<>();
        }
    }
}
@MappedJdbcTypes({JdbcType.VARCHAR})  //对应数据库类型
@MappedTypes({TokenSettings.class})            //java数据类型
public class TokenSettingsTypeHandler implements TypeHandler<TokenSettings> {


    private ObjectMapper objectMapper;

    public TokenSettingsTypeHandler() {
        objectMapper = new ObjectMapper();
        /**
         * 此处注册json存储格式化
         */
        ClassLoader classLoader = TokenSettingsTypeHandler.class.getClassLoader();
        List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
        this.objectMapper.registerModules(securityModules);
        this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, TokenSettings parameter, JdbcType jdbcType) throws SQLException {
        if(parameter!=null&&parameter.getSettings()!=null){
            ps.setString(i ,writeMap(parameter.getSettings()));
        }else{
            ps.setString(i, "");
        }

    }

    @Override
    public TokenSettings getResult(ResultSet rs, String columnName) throws SQLException {
        String str = rs.getString(columnName);
        return TokenSettings.withSettings(parseMap(str)).build();
    }

    @Override
    public TokenSettings getResult(ResultSet rs, int columnIndex) throws SQLException {
        String str = rs.getString(columnIndex);
        return TokenSettings.withSettings(parseMap(str)).build();
    }

    @Override
    public TokenSettings getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String str = cs.getString(columnIndex);
        return TokenSettings.withSettings(parseMap(str)).build();
    }

    private String writeMap(Map<String, Object> data) {
        try {
            this.objectMapper.findAndRegisterModules();
            return this.objectMapper.writeValueAsString(data);
        }
        catch (Exception ex) {
            throw new IllegalArgumentException(ex.getMessage(), ex);
        }
    }

    private Map<String, Object> parseMap(String data) {
        if(data!=null&&!data.isEmpty()){
            try {
                Map<String, Object> map = this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
                });
                return map;
            }
            catch (Exception ex) {
                throw new IllegalArgumentException(ex.getMessage(), ex);
            }
        }else{
            return new HashMap<>();
        }
    }
}

6)在repository包下,自定义SelfJdbcRegisteredClientRepository、SelfJdbcOAuth2AuthorizationService和SeltJdbcOAuth2AuthorizationConsentService

@Repository
public class SelfJdbcRegisteredClientRepository implements RegisteredClientRepository {

    @Autowired
    Oauth2RegisteredClientMapper mapper;

    @Override
    public void save(RegisteredClient registeredClient) {

        Assert.notNull(registeredClient, "registeredClient cannot be null");
        SelfRegisteredClient existingRegisteredClient = this.mapper.selectById(registeredClient.getId());
        if (existingRegisteredClient != null) {

            this.mapper.updateById(SelfRegisteredClient.covertSelfRegisteredClient(registeredClient));
        }
        else {
            this.mapper.insert(SelfRegisteredClient.covertSelfRegisteredClient(registeredClient));
        }

    }

    @Override
    public RegisteredClient findById(String id) {
        return SelfRegisteredClient.covertRegisteredClient(this.mapper.selectById(id));
    }

    @Override
    public RegisteredClient findByClientId(String clientId) {
        return SelfRegisteredClient.covertRegisteredClient(this.mapper.selectByClientId(clientId));
    }

    private void updateRegisteredClient(RegisteredClient registeredClient) {
        this.mapper.updateById(SelfRegisteredClient.covertSelfRegisteredClient(registeredClient));
    }

}
@Service
public class SelfJdbcOAuth2AuthorizationService implements OAuth2AuthorizationService {

    @Autowired
    private RegisteredClientRepository registeredClientRepository;

    @Autowired
    private OAuth2AuthorizationMapper oAuth2AuthorizationMapper;

    @Override
    public void save(OAuth2Authorization authorization) {
        Assert.notNull(authorization, "authorization cannot be null");
        OAuth2Authorization existingAuthorization = findById(authorization.getId());
        if (existingAuthorization == null) {
            oAuth2AuthorizationMapper.insert(SelfOAuth2Authorization.covertSelfOAuth2Authorization(authorization));
        }
        else {
            oAuth2AuthorizationMapper.updateById(SelfOAuth2Authorization.covertSelfOAuth2Authorization(authorization));
        }
    }

    @Override
    public void remove(OAuth2Authorization authorization) {
        oAuth2AuthorizationMapper.deleteById(SelfOAuth2Authorization.covertSelfOAuth2Authorization(authorization));
    }

    @Override
    public OAuth2Authorization findById(String id) {
        SelfOAuth2Authorization selfOAuth2Authorization = oAuth2AuthorizationMapper.selectById(id);
        return SelfOAuth2Authorization.covertOAuth2Authorization(selfOAuth2Authorization, registeredClientRepository);
    }

    @Override
    public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
        Assert.hasText(token, "token cannot be empty");
        List<SqlParameterValue> parameters = new ArrayList<>();
        List<SelfOAuth2Authorization> result = null;
        Map<String, Object> map = new HashMap<>();
        if (tokenType == null) {
            map.put("state", token);
            byte[] tokenBytes = token.getBytes(StandardCharsets.UTF_8);
            map.put("authorization_code_value", tokenBytes);
            map.put("access_token_value", tokenBytes);
            map.put("oidc_id_token_value", tokenBytes);
            map.put("refresh_token_value", tokenBytes);
            map.put("user_code_value", tokenBytes);
            map.put("device_code_value", tokenBytes);
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
            map.put("state", token);
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
            map.put("authorization_code_value", token.getBytes(StandardCharsets.UTF_8));
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
            map.put("access_token_value", token.getBytes(StandardCharsets.UTF_8));
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) {
            map.put("oidc_id_token_value", token.getBytes(StandardCharsets.UTF_8));
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
            map.put("refresh_token_value", token.getBytes(StandardCharsets.UTF_8));
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) {
            map.put("user_code_value", token.getBytes(StandardCharsets.UTF_8));
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) {
            map.put("device_code_value", token.getBytes(StandardCharsets.UTF_8));
            result = oAuth2AuthorizationMapper.selectByMap(map);
        }
        return result!=null&&!result.isEmpty()?SelfOAuth2Authorization.covertOAuth2Authorization(result.get(0),registeredClientRepository):null;
    }
}
@Service
public class SeltJdbcOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {

    @Autowired
    private OAuth2AuthorizationConsentMapper auth2AuthorizationConsentMapper;

    @Autowired
    private RegisteredClientRepository registeredClientRepository;

    @Override
    public void save(OAuth2AuthorizationConsent authorizationConsent) {
        Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
        OAuth2AuthorizationConsent existingAuthorizationConsent = findById(authorizationConsent.getRegisteredClientId(),
                authorizationConsent.getPrincipalName());
        if (existingAuthorizationConsent == null) {
            auth2AuthorizationConsentMapper.insert(SelfOAuth2AuthorizationConsent.convertSelfOAuth2AuthorizationConsent(authorizationConsent));
        }
        else {
            auth2AuthorizationConsentMapper.updateById(SelfOAuth2AuthorizationConsent.convertSelfOAuth2AuthorizationConsent(authorizationConsent));
        }
    }

    @Override
    public void remove(OAuth2AuthorizationConsent authorizationConsent) {
        auth2AuthorizationConsentMapper.deleteById(SelfOAuth2AuthorizationConsent.convertSelfOAuth2AuthorizationConsent(authorizationConsent));
    }

    @Override
    public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
        Map<String, Object> map = new HashMap<>();
        map.put("registered_client_id", registeredClientId);
        map.put("principal_name", principalName);
        List<SelfOAuth2AuthorizationConsent> list = auth2AuthorizationConsentMapper.selectByMap(map);
        return list==null||list.isEmpty()?null:SelfOAuth2AuthorizationConsent.convertOAuth2AuthorizationConsent(list.get(0), registeredClientRepository);
    }

}

7)在config包下,配置SecurityConfig,这个和lesson03子模块很像,去除自定义授权页定义即可

@Configuration
public class SecurityConfig {

    // 自定义授权服务器的Filter链
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // oidc配置
                .oidc(withDefaults())
        ;
        // 资源服务器默认jwt配置
        http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults()));
        // 异常处理
        http.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(
                new LoginUrlAuthenticationEntryPoint("/login")));
        return http.build();
    }

    // 自定义Spring Security的链路。如果自定义授权服务器的Filter链,则原先自动化配置将会失效,因此也要配置Spring Security
    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/demo", "/test").permitAll()
                .anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }

}

8)在resources包下,配置化application.yml文件(注意:要在mybatis-plus配置下注册ypeHandler)

server:
  port: 9000

logging:
  level:
    org.springframework.security: trace

spring:
  security:
    # 使用security配置授权服务器的登录用户和密码
    user:
      name: user
      password: 1234

  # 配置数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/oauth_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root
    druid:
      initial-size: 5
      min-idle: 5
      maxActive: 20
      maxWait: 3000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: select 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: false
      filters: stat,wall,slf4j
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;socketTimeout=10000;connectTimeout=1200

# mybatis-plus的配置
mybatis-plus:
  global-config:
    banner: false
  mapper-locations: classpath:mappers/*.xml
  type-aliases-package: com.demo.lesson04.entity
  # 将handler包下的TypeHandler注册进去
  type-handlers-package: com.demo.lesson04.handler
  configuration:
    cache-enabled: false
    local-cache-scope: statement

9)在controller包下,定义InsertController,通过接口方式注册一个和lesson03一样信息的客户端

@RestController
public class InsertController{

    @Autowired
    Oauth2RegisteredClientMapper oauth2RegisteredClientMapper;

    @GetMapping("/insert")
    public void insert(){
        SelfRegisteredClient client = new SelfRegisteredClient();
        client.setId(UUID.randomUUID().toString());
        client.setClientId("oidc-client");
        client.setClientSecret("{noop}secret");
        client.setClientName("oidc-client");
        Set<ClientAuthenticationMethod> methodSet = new HashSet<>();
        client.setClientAuthenticationMethods(new HashSet<>(Collections.singleton(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())));
        Set<String> typeSet = new HashSet<>();
        typeSet.add(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
        typeSet.add(AuthorizationGrantType.REFRESH_TOKEN.getValue());
        client.setAuthorizationGrantTypes(typeSet);
        client.setRedirectUris(new HashSet<>(Collections.singleton("http://localhost:8080/login/oauth2/code/oidc-client")));
        client.setPostLogoutRedirectUris(new HashSet<>(Collections.singleton("http://localhost:8080/")));
        Set<String> scopeSet = new HashSet<>();
        scopeSet.add(OidcScopes.OPENID);
        scopeSet.add(OidcScopes.PROFILE);
        client.setScopes(scopeSet);
        client.setClientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build());
        client.setTokenSettings(TokenSettings.builder().build());

        oauth2RegisteredClientMapper.insert(client);
        System.out.println(client);
    }
}

10)新建启动类Oauth2Lesson04Application,并启动

@SpringBootApplication
public class Oauth2Lesson04Application {

    public static void main(String[] args) {
        SpringApplication.run(Oauth2Lesson04Application.class, args);
    }
}

11)启动lesson02子模块的oauth-client子模块,作为演示客户端

12)插入客户端,访问http://localhost:9000/insert 插入一个客户端,其实就是把原先在yaml配置的客户端插入到数据库。我们会看到数据库表oauth_study.oauth2_registered_client插入一条记录

13)测试,访问:http://localhost:8080/demo 你就可以看到与《系列之四 - 客户端–oauth2-client底层原理》一样的流程,只不过其客户端信息是在数据库中(你可以一步一步操作,看看数据库三张表数据的变化)。

结语:在本章及之前章节,我们对Spring Security实现OAuth2做了一个基本了解,也通过自定义授权页面、自定义数据库客户端信息等窥探了Spring Authrization Server,下一章,我们将会讲述Spring Authrization Server的关键原理代码以及一些关键的Filter,这样对我们后面做更高级的配置有一个很好的了解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vim-plug的自动安装与基本使用介绍

vim-plug介绍 Vim-plug 是一个轻量级的 Vim 插件管理器&#xff0c;它允许你轻松地管理 Vim 插件的安装、更新和卸载。相较于其他插件管理器&#xff0c;vim-plug 的优点是简单易用&#xff0c;速度较快&#xff0c;而且支持懒加载插件&#xff08;即按需加载&#xff09; 自动…

Deep Crossing:深度交叉网络在推荐系统中的应用

实验和完整代码 完整代码实现和jupyter运行&#xff1a;https://github.com/Myolive-Lin/RecSys--deep-learning-recommendation-system/tree/main 引言 在机器学习和深度学习领域&#xff0c;特征工程一直是一个关键步骤&#xff0c;尤其是对于大规模的推荐系统和广告点击率预…

想品客老师的第十天:类

类是一个优化js面向对象的工具 类的声明 //1、class User{}console.log(typeof User)//function//2、let Hdclass{}//其实跟1差不多class Stu{show(){}//注意这里不用加逗号&#xff0c;对象才加逗号get(){console.log(后盾人)}}let hdnew Stu()hd.get()//后盾人 类的原理 类…

MyBatis-Plus速成指南:条件构造器和常用接口

Wrapper 介绍 Wrapper&#xff1a;条件构造抽象类&#xff0c;最顶端父类 AbstractWrapper&#xff1a;用于查询条件封装&#xff0c;生成 SQL 的 where 条件QueryWrapper&#xff1a;查询条件封装UpdateWrapper&#xff1a;Update 条件封装AbstractLambdaWrapper&#xff1a;使…

(脚本学习)BUU18 [CISCN2019 华北赛区 Day2 Web1]Hack World1

自用 题目 考虑是不是布尔盲注&#xff0c;如何测试&#xff1a;用"1^1^11 1^0^10&#xff0c;就像是真真真等于真&#xff0c;真假真等于假"这个测试 SQL布尔盲注脚本1 import requestsurl "http://8e4a9bf2-c055-4680-91fd-5b969ebc209e.node5.buuoj.cn…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.25 多线程并行:GIL绕过与真正并发

2.25 多线程并行&#xff1a;GIL绕过与真正并发 目录 #mermaid-svg-JO4lsTIyjOweVkos {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JO4lsTIyjOweVkos .error-icon{fill:#552222;}#mermaid-svg-JO4lsTIyjOweVkos …

Java 大视界 -- Java 大数据在智能医疗影像诊断中的应用(72)

💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也期待你毫无保留地分享独特见解,愿我们于此携手成长,共赴新程!💖 一、…

【Leetcode刷题记录】1456. 定长子串中元音的最大数目---定长滑动窗口即解题思路总结

1456. 定长子串中元音的最大数目 给你字符串 s 和整数 k 。请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。 英文中的 元音字母 为&#xff08;a, e, i, o, u&#xff09;。 这道题的暴力求解的思路是通过遍历字符串 s 的每一个长度为 k 的子串&#xf…

upload-labs安装与配置

前言 作者进行upload-labs靶场练习时&#xff0c;在环境上出了很多问题&#xff0c;吃了很多苦头&#xff0c;甚至改了很多配置也没有成功。 upload-labs很多操作都是旧时代的产物了&#xff0c;配置普遍都比较老&#xff0c;比如PHP版本用5.2.17&#xff08;还有中间件等&am…

从Transformer到世界模型:AGI核心架构演进

文章目录 引言:架构革命推动AGI进化一、Transformer:重新定义序列建模1.1 注意力机制的革命性突破1.2 从NLP到跨模态演进1.3 规模扩展的黄金定律二、通向世界模型的关键跃迁2.1 从语言模型到认知架构2.2 世界模型的核心特征2.3 混合架构的突破三、构建世界模型的技术路径3.1 …

每日一博 - 三高系统架构设计:高性能、高并发、高可用性解析

文章目录 引言一、高性能篇1.1 高性能的核心意义1.2 影响系统性能的因素1.3 高性能优化方法论1.3.1 读优化&#xff1a;缓存与数据库的结合1.3.2 写优化&#xff1a;异步化处理 1.4 高性能优化实践1.4.1 本地缓存 vs 分布式缓存1.4.2 数据库优化 二、高并发篇2.1 高并发的核心意…

【工欲善其事】利用 DeepSeek 实现复杂 Git 操作:从原项目剥离出子版本树并同步到新的代码库中

文章目录 利用 DeepSeek 实现复杂 Git 操作1 背景介绍2 需求描述3 思路分析4 实现过程4.1 第一次需求确认4.2 第二次需求确认4.3 第三次需求确认4.4 V3 模型&#xff1a;中间结果的处理4.5 方案验证&#xff0c;首战告捷 5 总结复盘 利用 DeepSeek 实现复杂 Git 操作 1 背景介绍…

【C++】线程池实现

目录 一、线程池简介线程池的核心组件实现步骤 二、C11实现线程池源码 三、线程池源码解析1. 成员变量2. 构造函数2.1 线程初始化2.2 工作线程逻辑 3. 任务提交(enqueue方法)3.1 方法签名3.2 任务封装3.3 任务入队 4. 析构函数4.1 停机控制 5. 关键技术点解析5.1 完美转发实现5…

数据结构实战之线性表(三)

目录 1.顺序表释放 2.顺序表增加空间 3.合并顺序表 4.线性表之链表实现 1.项目结构以及初始代码 2.初始化链表(不带头结点) 3.链表尾部插入数据并显示 4.链表头部插入数据 5.初始化链表&#xff08;带头结点&#xff09; 6.带头结点的链表头部插入数据并显示 7.带头结…

【python】python基于机器学习与数据分析的手机特性关联与分类预测(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 python基于机器学习与数据分析的手机特性关联与分类…

ZOJ 1007 Numerical Summation of a Series

原题目链接 生成该系列值的表格 对于x 的 2001 个值&#xff0c;x 0.000、0.001、0.002、…、2.000。表中的所有条目的绝对误差必须小于 0.5e-12&#xff08;精度为 12 位&#xff09;。此问题基于 Hamming (1962) 的一个问题&#xff0c;当时的大型机按今天的微型计算机标准来…

全面解析文件上传下载删除漏洞:风险与应对

在数字化转型的时代&#xff0c;文件上传、下载与删除功能已经成为各类应用程序的标准配置&#xff0c;从日常办公使用的协同平台&#xff0c;到云端存储服务&#xff0c;再到社交网络应用&#xff0c;这些功能在给用户带来便捷体验、显著提升工作效率的同时&#xff0c;也隐藏…

【C语言深入探索】结构体详解(二):使用场景

目录 一、复杂数据的表示 二、数据的封装 三、多态的模拟 四、回调函数的实现 五、多线程编程 六、通信协议的实现和文件操作 6.1. 使用结构体实现简单通信协议 6.2. 使用结构体进行文件操作 七、图形界面编程 结构体在C语言中具有广泛的应用场景&#xff0c;以下是一…

【大模型】AI 辅助编程操作实战使用详解

目录 一、前言 二、AI 编程介绍 2.1 AI 编程是什么 2.1.1 为什么需要AI辅助编程 2.2 AI 编程主要特点 2.3 AI编程底层核心技术 2.4 AI 编程核心应用场景 三、AI 代码辅助编程解决方案 3.1 AI 大模型平台 3.1.1 AI大模型平台代码生成优缺点 3.2 AI 编码插件 3.3 AI 编…

RK3566-移植5.10内核Ubuntu22.04

说明 记录了本人使用泰山派&#xff08;RK3566&#xff09;作为平台并且成功移植5.10.160版本kernel和ubuntu22.04&#xff0c;并且成功配置&连接网络的完整过程。 本文章所用ubuntu下载地址&#xff1a;ubuntu-cdimage-ubuntu-base-releases-22.04-release安装包下载_开源…