基于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 客户端认证原理](#1 客户端认证原理)
  • [2 Spring Authrization Server客户端表说明](#2 Spring Authrization Server客户端表说明)
  • [3 基于数据库客户端](#3 基于数据库客户端)
    • [3.1 自带的Jdbc实现类](#3.1 自带的Jdbc实现类)
    • [3.2 使用自带的Jdbc实现](#3.2 使用自带的Jdbc实现)
  • [4 基于自定义数据库客户端](#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表的记录,这样下次就不用再次授权
  • 客户端信息表:保存客户端信息的,可以参考一下注解大概知道其字段含义
sql 复制代码
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)
);
  • 授权信息表:授权信息,在跳转到授权页面时,会插入一条信息。
sql 复制代码
/*
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)
);
  • 确认授权表:授权确认后,就会将客户端表的记录与授权信息表的记录关联在一起,并记录授权情况
sql 复制代码
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

java 复制代码
@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引入如下:

xml 复制代码
<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)

java 复制代码
@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;
    }

}
java 复制代码
@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;
    }
}
java 复制代码
@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 表

java 复制代码
@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);
}
java 复制代码
@Mapper
public interface OAuth2AuthorizationMapper extends BaseMapper<SelfOAuth2Authorization> {
}
java 复制代码
@Mapper
public interface OAuth2AuthorizationConsentMapper extends BaseMapper<SelfOAuth2AuthorizationConsent> {
}

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

java 复制代码
@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<>();
        }
    }
}
java 复制代码
@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;
    }
}
java 复制代码
@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<>();
        }
    }
}
java 复制代码
@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

java 复制代码
@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));
    }

}
java 复制代码
@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;
    }
}
java 复制代码
@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子模块很像,去除自定义授权页定义即可

java 复制代码
@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)

yaml 复制代码
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一样信息的客户端

java 复制代码
@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,并启动

java 复制代码
@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,这样对我们后面做更高级的配置有一个很好的了解。

相关推荐
IT 行者4 天前
Spring Security 6.x 迁移到 7.0 的完整步骤
java·spring·oauth2
IT界的奇葩9 天前
OAuth2 单点登录流程图
java·流程图·oauth2·单点登录·sso
泽济天下23 天前
【经验分享】基于Spring Boot 4.0快速实现最简版的OAuth2 Server和Client
spring boot·springboot·oauth2
佛祖让我来巡山1 个月前
小明网站双登录系统实现——微信授权登录+用户名密码登录完整指南
oauth2·springsecurity·微信授权登录
佛祖让我来巡山1 个月前
小明网站微信登录改造记——OAuth2完整指南(含续期逻辑)
oauth2·微信授权登录
不会吃萝卜的兔子1 个月前
spring - 微服务授权 2 实战
spring·oauth2·authorization
陈果然DeepVersion2 个月前
Java大厂面试真题:从Spring Boot到AI微服务的三轮技术拷问(一)
java·spring boot·redis·微服务·kafka·面试题·oauth2
asom222 个月前
互联网大厂Java求职面试实战:Spring Boot到Kubernetes的技术问答
java·spring boot·kubernetes·oauth2·电商·microservices·面试技巧
Mysticbinary2 个月前
OAuth2 协议解析(安全视角)
oauth2·鉴权协议
Jabes.yang2 个月前
Java面试大作战:从缓存技术到音视频场景的探讨
java·spring boot·redis·缓存·kafka·spring security·oauth2