当前位置: 首页 > article >正文

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

之所以想写这一系列,是因为之前工作过程中使用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.kler.cn/a/527027.html

相关文章:

  • 反向代理模块b
  • 绝对值线性化
  • 计算机网络一点事(22)
  • 996引擎 - NPC-添加NPC引擎自带形象
  • Transformer+vit原理分析
  • 如何解决Unit sshd.service could not be found
  • Git进阶之旅:Git 多人合作
  • 13JavaWeb——SpringBootWeb之事务AOP
  • Redis地理散列GeoHash
  • 开源智慧园区管理系统如何重塑企业管理模式与运营效率
  • 适合传输政府保密文档的可替代FTP传输系统
  • 数据挖掘常用算法
  • Redis篇 Redis如何清理过期的key以及对应的解决方法
  • Vue - 标签中 ref 属性的使用
  • C# Winform制作一个登录系统
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.1 NumPy高级索引:布尔型与花式索引的底层原理
  • CAP 定理的 P 是什么
  • 具有HiLo注意力的快速视觉Transformer
  • 从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(绘图设备封装)
  • qt之数据库的使用二
  • grpc 和 http 的区别---二进制vsJSON编码
  • 青少年编程与数学 02-008 Pyhon语言编程基础 10课题、列表与循环语句
  • 替代传统汽车行业FTP传输方式,实现数字化重大转型
  • 本地部署DeepSeekp R1教程
  • 如何用KushoAI提升API自动化测试效率:AI驱动的革命
  • FreeRTOS学习 --- 任务调度