鸭鸭笔记-Spring Authorization Server

为什么使用Spring Authorization Server?

因为公司一个项目使用的第三方供应商提供的身份认证系统老是挂掉,不稳定.所以决定自主搭建.

由于Spring Security OAuth已经停止维护,而Spring Authorization Server却是在其基础上的增强和扩展,搭建简单方便,支持OAuth2.0,OIDC

Spring Authorization Server 基础

介绍

Spring Authorization Server 是一个基于 Spring Framework 的授权服务器,它提供了一种简单的方式来保护服务的资源.它是一个开源项目,可以快速构建和部署授权服务器.

使用 Spring Authorization Server 有以下几个好处:

  1. 安全性高 Spring Authorization Server 可以与 Spring SecurityOAuth 2.0 协议一起使用,以提供安全的身份验证和授权机制.它支持多种授权流程,包括 Authorization Code、Implicit、Client CredentialsPassword 等.这些授权流程都是经过安全验证的,可以保证应用程序的安全性.

  2. 易于使用 Spring Authorization Server 提供了一些可扩展的功能,例如自定义令牌生成和存储、多租户支持等.这些功能可以帮助您轻松地创建和管理客户端应用程序,并为这些应用程序分配不同的权限.此外,它还提供了一些易于使用的API和UI,可以快速构建和部署授权服务器.

  3. 高度可定制 Spring Authorization Server 是一个高度可定制的授权服务器,可以根据需求进行定制.可以使用自定义令牌生成和存储、多租户支持等功能来满足特定需求.此外,它还提供了一些可扩展的插件和扩展点,可以扩展其功能.

授权模式

授权码模式

authorization_code 这是最常用的授权类型,适用于需要用户交互的客户端应用程序.在这种模式下,客户端首先将用户重定向到授权服务器的授权页面,用户在授权页面上输入凭据并授权.然后,授权服务器将授权码发送给客户端.客户端使用授权码向授权服务器请求访问令牌.这种模式的优点是可以避免将访问令牌直接暴露给用户,提高了安全性.

sequenceDiagram 客户端->>授权服务器: 请求授权码 授权服务器-->>客户端: 弹出登录页 客户端->>授权服务器: 输入账号密码 授权服务器->授权服务器:验证用户凭据 授权服务器-->>客户端: 通过redirectUri返回授权码 客户端->>授权服务器: 使用授权码请求token 授权服务器->授权服务器:验证授权码,客户端 授权服务器-->>客户端: 返回token,refresh_token 客户端->>资源服务器: 携带token请求资源 资源服务器->>授权服务器: 验证token 授权服务器->>授权服务器: 验证token正确性,有效性 授权服务器->>资源服务器: 验证通过 资源服务器-->>客户端: 返回资源

客户端凭据模式

client_credentials 这种授权类型适用于没有用户交互的后台服务.在这种模式下,客户端使用自己的凭据(客户端 ID 和密钥)向授权服务器请求访问令牌.这种模式的优点是简单且安全性较高,但不适用于需要用户授权的场景.

sequenceDiagram 客户端->>授权服务器:发送clientId和clientSecret 授权服务器->>客户端:返回Token 客户端->>资源服务器: 携带token请求资源 资源服务器->>授权服务器: 验证token 授权服务器->>授权服务器: 验证token正确性,有效性 授权服务器->>资源服务器: 验证通过 资源服务器-->>客户端: 返回资源

令牌刷新模式

refresh_token 用于在访问令牌过期时获取新的访问令牌.当客户端用 access_token 向资源服务器请求资源时,如果 access_token 已过期,则资源服务器将返回 401 Unauthorized 响应.此时,客户端可以使用 refresh_token 向授权服务器请求新的 access_token . 在 OAuth2.0 协议中,refresh_token 是在授权码授权、密码授权和客户端凭据授权等授权类型中使用的.当客户端向授权服务器请求访问令牌时,授权服务器可以返回一个 refresh_token ,客户端可以使用该 refresh_tokenaccess_token 过期时获取新的 access_token.

sequenceDiagram 客户端->>授权服务器:发送refresh_token 授权服务器->>客户端:返回Token

密码模式

password 这种授权类型适用于与用户有高度信任关系的应用程序.在这种模式下,客户端直接收集用户的用户名和密码,并使用这些凭据向授权服务器请求访问令牌.这种模式的缺点是需要用户将凭据提供给客户端,安全性较低.该模式在OAuth2.1中已移除,如果有场景需要这种模式,可以自行扩展,后续会讲到.

sequenceDiagram 客户端->>授权服务器:发送username和password 授权服务器->>客户端:返回Token 客户端->>资源服务器: 携带token请求资源 资源服务器->>授权服务器: 验证token 授权服务器->>授权服务器: 验证token正确性,有效性 授权服务器->>资源服务器: 验证通过 资源服务器-->>客户端: 返回资源

JWT令牌授权模式

urn:ietf:params:oauth:grant-type:jwt-bearer 授权类型是 OAuth2.0 协议的一种类型,允许客户端使用 JSON Web Token (JWT) 交换访问令牌.

在这种授权类型中,客户端在向授权服务器的令牌端点请求访问令牌时包含一个JWT,JWT包含一组声明,用于声明客户端和代表客户端发出请求的用户的身份.授权服务器验证JWT,如果有效,则向客户端发放访问令牌.

这种授权类型在客户端已经拥有包含获取访问令牌所需信息的JWT的情况下非常有用,例如当客户端通过单独的身份验证过程获得JWT时.它也可以用于客户端希望将用户身份验证委托给第三方身份提供者的情况. 总的来说,urn:ietf:params:oauth:grant-type:jwt-bearer 授权类型提供了一种安全高效的方式,使客户端可以使用JWT获取访问令牌.它在各种场景下都非常有用,Spring Authorization Server 提供了对此授权类型的支持.

这个模式的优点是:

  1. 简化了授权流程.不需要先拿code换token,直接使用JWT获取访问令牌.
  2. 客户端可以在JWT中携带更丰富的信息,传递给授权服务器.授权服务器可以根据这些信息判断是否授权.
  3. 不需要通过重定向携带code,更适合非浏览器客户端.

这个模式要求:

  1. 客户端和授权服务器之间要有共享的密钥用于签名JWT.
  2. 授权服务器要支持验证JWT的签名和内容.
  3. 客户端创建的JWT必须包含足够的信息让授权服务器判断是否授权.

一个使用场景是:

机器到机器的应用,客户端和授权服务器之间有预共享密钥。客户端可以在JWT中包含相当详细的调用凭据和范围,让授权服务器基于这些信息直接授权和返回访问令牌。这是一个更高效和安全的授权模式,但要求客户端和授权服务器有更高层次的信任关系。对于不太信任的客户端,授权服务器可能更倾向使用标准的授权码模式

设备码授权模式

urn:ietf:params:oauth:grant-type:device_code它适用于无法直接在设备上进行授权的场景,例如智能电视、游戏机等。使用该授权方式,用户需要在另一台设备上进行授权,然后将授权码输入到设备上进行登录.

这个模式的优点是:

  1. 适用于那些无法在浏览器中打开授权页面的设备,比如智能电视,IoT设备等.使用device_code可以完成授权流程.
  2. 不需要重定向,更加安全和流畅,device_code只能在设备上使用一次.
  3. 同时也允许用户在另一个设备(比如手机)上输入device_code完成授权,更加灵活.

这个模式的要求是:

  1. 客户端必须在自己的界面上向用户显示device_code,并提示用户如何使用它.
  2. 授权服务器必须实现device_code的申请、验证和换取访问令牌的接口.
  3. 授权服务器生成的device_code必须具有足够的安全性,且能够在有限时间内使用一次.

设备授权模式是一种更加方便和安全的机器设备授权方式,适用于那些无法进行标准浏览器授权流程的场景.但它要求客户端和授权服务器实现较为复杂的交互流程,并确保device_code的安全性.

客户端授权方法

client_secret

这是默认的客户端认证方法.客户端通过请求参数client_idclient_secret来认证自己.适用于机密客户端.

client_secret_post

客户端通过请求体中的client_idclient_secret来认证自己.其他与client_secret相同.

client_secret_basic

客户端通过基本认证头的账号(client_id)和密码(client_secret)来认证自己.其他与client_secret相同.

client_secret_jwt

客户端使用私钥签名一个JWT,然后在请求中提交这个JWT来认证自己.JWT的payload需要包含client_id,audience和issuer。适用于公开客户端.

none

不进行客户端认证.只在高度可信的环境使用,因为任何客户端都可以获得访问令牌.

private_key_jwt

和client_secret_jwt类似,但是使用RSA或ECDSA算法的私钥签名JWT.提供更高的安全性,因为私钥更加难以破解.

API调用

这里注意一下,一开始我也掉坑了,客户端如果在注册的时候使用的是ClientAuthenticationMethod.CLIENT_SECRET_BASIC,那么api请求header 中要加上Authorization :Basic Base64.encode(client_id:client_secret).其他方式根据上文提到放在对应请求体中即可.授权服务器将会通过这个值验证客户端的有效性.

获取授权码

http 复制代码
# client_id 客户端ID
# response_type 响应模式
# scope 授权范围(不清楚可以百度一下)
# redirect_uri 回调地址,code会通过这个地址返回

GET http://127.0.0.1:8084/oauth2/authorize?client_id=sas-client&response_type=code&scope=message.read+openid&redirect_uri=http://127.0.0.1:8084/test
Authorization: Basic c2FzLWNsaWVudDpzYXMtc2VjcmV0

使用授权码token

http 复制代码
# grant_type 授权模式
# code 你的授权码
# redirect_uri 回调地址,请求code时所传参数

POST http://127.0.0.1:8084/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic c2FzLWNsaWVudDpzYXMtc2VjcmV0

grant_type=authorization_code&code={code}&redirect_uri=http://127.0.0.1:8084/test

账号密码获取token

http 复制代码
# grant_type 授权模式
# username 账号
# password 密码
# scope 授权范围(不清楚可以百度一下)

POST http://127.0.0.1:8084/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic c2FzLWNsaWVudDpzYXMtc2VjcmV0

grant_type=password&username={username}&password={password}

客户端获取token

http 复制代码
# grant_type 授权模式

POST http://127.0.0.1:8084/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic c2FzLWNsaWVudDpzYXMtc2VjcmV0

grant_type=client_credentials

刷新token

http 复制代码
# grant_type 授权模式
# refresh_token refresh_token

POST http://127.0.0.1:8084/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic c2FzLWNsaWVudDpzYXMtc2VjcmV0

grant_type=refresh_token&refresh_token={refresh_token}

Spring Authorization Server 集成

maven引用

这里使用的是JDK17 ,spring boot的版本是3.1.0.

java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
java 复制代码
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>1.1.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-cas</artifactId>
    <version>6.1.0</version>
</dependency>

数据库创建

sas核心的数据库创建有三个方法:

从官方demo或者sql文件

text 复制代码
在这三个路径下:

org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql

org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql

org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql

从官网获取sql

How-to: Implement core services with JPA

官网的很全面,不仅有创建表单的sql,实体类的创建也有,简直是我等的福音,统统CV😎

从我这里CV

SQL 复制代码
CREATE TABLE oauth2_registered_client
(
    id                            varchar(100)                            NOT NULL,
    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_settings                varchar(2000)                           NOT 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)
);
SQL 复制代码
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)
);

作为专业CV工程师,我当然懂原地CV有多美好了,这三张表是支持sas基本功能的表,没有跑不起来,至于作用,把表名直接翻译就知道是干嘛用的了.

配置

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.session.HttpSessionEventPublisher;

@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class DefaultSecurityConfig {

   @Bean
   @Order(2)
   public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
      http
         .authorizeHttpRequests(authorize ->
            authorize
               .requestMatchers("/login").permitAll()
               .anyRequest().authenticated()
         )
         .formLogin(formLogin ->
            formLogin
               .loginPage("/login")
         )
         .oauth2Login(oauth2Login ->
            oauth2Login
               .loginPage("/login")
         );

      return http.build();
   }
   
   @Bean
   public UserDetailsService users() {
      UserDetails user = User.withDefaultPasswordEncoder()
            .username("user1")
            .password("password")
            .roles("USER")
            .build();
      return new InMemoryUserDetailsManager(user);
   }

   @Bean
   public SessionRegistry sessionRegistry() {
      return new SessionRegistryImpl();
   }

   @Bean
   public HttpSessionEventPublisher httpSessionEventPublisher() {
      return new HttpSessionEventPublisher();
   }

}
java 复制代码
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.time.Duration;

@Configuration
public class AuthorizationServerConfig {

    private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 关于Protocol Endpoints.(协议端点)配置
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        return defaultConfig(http);
    }

    public DefaultSecurityFilterChain minConfig(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(
                                new LoginUrlAuthenticationEntryPoint("/login"))
                ).build();

    }

    public DefaultSecurityFilterChain defaultConfig(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .tokenEndpoint(oAuth2TokenEndpointConfigurer -> oAuth2TokenEndpointConfigurer
                        .accessTokenRequestConverter(new Oauth2PasswordAuthenticationConverter())
                        .accessTokenRequestConverter(new Oauth2SmsCodeAuthenticationConverter()))

                .authorizationEndpoint(authorizationEndpoint ->
                        authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
                .oidc(Customizer.withDefaults())
        ;
        http
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                .oauth2ResourceServer(oauth2ResourceServer ->
                        oauth2ResourceServer.jwt(Customizer.withDefaults()))
                .csrf(CsrfConfigurer::disable);
        return http.build();
    }

    /**
     * 启动时创建客户端
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(String.valueOf(SnowFlakeUtils.getInstance().nextId()))
                .clientId("sas-client")
                .clientSecret(passwordEncoder().encode("sas-secret"))
                .clientName("sas测试客户端")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(new AuthorizationGrantType(CustomGrantType.PASSWORD))
                .redirectUri("http://127.0.0.1:8084/test")
                .postLogoutRedirectUri("http://127.0.0.1:8084/logged-out")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("message.read")
                .scope("message.write")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                // token配置项
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofMinutes(100L))
                        // 使用默认JWT相关格式
                        .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
                        .reuseRefreshTokens(true)
                        .refreshTokenTimeToLive(Duration.ofMinutes(120L))
                        .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256).build()
                )
                .build();

        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        //判断客户端是否已经存在,不存在即创建
        if (null == registeredClientRepository.findByClientId("sas-client")) {
            registeredClientRepository.save(registeredClient);
        }
        return registeredClientRepository;
    }

    /**
     * 关于OAuth2Authorization 授权信息的处理(入库)
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
                                                           RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 关于OAuth2AuthorizationConsent信息的处理(入库)
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
                                                                         RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
        return new FederatedIdentityTokenCustomizer();
    }

    /**
     * 关于token生成规则的处理
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = JwksUtils.generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * 关于AuthorizationServerSettings【授权服务器】的配置,含路径及接口
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

}

所用到的工具类

java 复制代码
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;

final class KeyGeneratorUtils {
    private KeyGeneratorUtils() {
    }

    static SecretKey generateSecretKey() {
        SecretKey hmacKey;
        try {
            hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return hmacKey;
    }

    static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    static KeyPair generateEcKey() {
        EllipticCurve ellipticCurve = new EllipticCurve(
                new ECFieldFp(
                        new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
                new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
                new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
        ECPoint ecPoint = new ECPoint(
                new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
                new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
        ECParameterSpec ecParameterSpec = new ECParameterSpec(
                ellipticCurve,
                ecPoint,
                new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
                1);

        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
            keyPairGenerator.initialize(ecParameterSpec);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }
}
java 复制代码
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

public final class JwksUtils {
    private JwksUtils() {
    }

    public static RSAKey generateRsa() {
        KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
    }

    public static ECKey generateEc() {
        KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
        Curve curve = Curve.forECParameterSpec(publicKey.getParams());
        return new ECKey.Builder(curve, publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
    }

    public static OctetSequenceKey generateSecret() {
        SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
        return new OctetSequenceKey.Builder(secretKey)
                .keyID(UUID.randomUUID().toString())
                .build();
    }
}

总结

从6月份就写了,到现在还没写完,大家不会怪我吧.扩展部分放到下一章吧,这篇字数够多了,不足之处请大家指出,谢谢!

相关推荐
没有bug.的程序员5 分钟前
电商系统分布式架构实战:从单体到微服务的演进之路
java·分布式·微服务·云原生·架构·监控体系·指标采集
Query*14 分钟前
Java 设计模式——代理模式:从静态代理到 Spring AOP 最优实现
java·设计模式·代理模式
梵得儿SHI16 分钟前
Java 反射机制深度解析:从对象创建到私有成员操作
java·开发语言·class对象·java反射机制·操作类成员·三大典型·反射的核心api
JAVA学习通20 分钟前
Spring AI 核心概念
java·人工智能·spring·springai
望获linux22 分钟前
【实时Linux实战系列】实时 Linux 在边缘计算网关中的应用
java·linux·服务器·前端·数据库·操作系统
绝无仅有30 分钟前
面试真实经历某商银行大厂数据库MYSQL问题和答案总结(二)
后端·面试·github
绝无仅有32 分钟前
通过编写修复脚本修复 Docker 启动失败(二)
后端·面试·github
..Cherry..34 分钟前
【java】jvm
java·开发语言·jvm
老K的Java兵器库43 分钟前
并发集合踩坑现场:ConcurrentHashMap size() 阻塞、HashSet 并发 add 丢数据、Queue 伪共享
java·后端·spring
冷冷的菜哥1 小时前
go邮件发送——附件与图片显示
开发语言·后端·golang·邮件发送·smtp发送邮件