4. OAuth2.0 授权服务器搭建:使用 Spring Authorization Server

本章将基于 Spring Authorization Server (SAS) 搭建一个标准的 OAuth2.0 授权服务器,并完成客户端的注册与配置。SAS 是 Spring 官方提供的、替代旧版 Spring Security OAuth2 的现代授权服务器框架,原生支持授权码、客户端凭证、刷新令牌等流程。

4.1 核心依赖与项目结构

授权服务器基于 Spring Boot 3.x + Spring Authorization Server 1.x 构建。核心 Maven 依赖如下:

<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.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>1.2.0</version>
</dependency>
版本说明: SAS 1.2.0 对应 Spring Boot 3.1.x,支持 JDK 17+。生产环境建议锁定版本号,避免依赖冲突。

4.2 授权服务器配置类

通过 @Configuration 类启用授权服务器,并配置安全过滤链与端点。以下为最小化配置:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(Customizer.withDefaults()); // 启用 OpenID Connect 1.0
        http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        http.exceptionHandling(exceptions ->
            exceptions.authenticationEntryPoint(
                new LoginUrlAuthenticationEntryPoint("/login")));
        return http.build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        // 客户端注册信息存储(内存示例)
        return new InMemoryRegisteredClientRepository(buildClient());
    }

    private RegisteredClient buildClient() {
        return RegisteredClient.withId("client-1")
            .clientId("message-queue-client")
            .clientSecret("{noop}secret-123")
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .redirectUri("http://127.0.0.1:8080/login/oauth2/code/sas")
            .scope("message:read")
            .scope("message:write")
            .clientSettings(ClientSettings.builder()
                .requireAuthorizationConsent(true)
                .build())
            .tokenSettings(TokenSettings.builder()
                .accessTokenTimeToLive(Duration.ofMinutes(30))
                .refreshTokenTimeToLive(Duration.ofDays(1))
                .build())
            .build();
    }
}

4.3 客户端注册参数详解

客户端注册是授权服务器与调用方建立信任关系的关键步骤。下表列出核心配置项及其含义:

配置项 示例值 说明
clientId message-queue-client 客户端唯一标识,用于认证请求
clientSecret {noop}secret-123 客户端密钥;{noop} 表示明文,生产应使用 {bcrypt}
authorizationGrantType AUTHORIZATION_CODE / CLIENT_CREDENTIALS 允许的授权类型,可注册多个
redirectUri http://127.0.0.1:8080/login/oauth2/code/sas 授权码模式回调地址,必须精确匹配
scope message:read, message:write 客户端可请求的权限范围
requireAuthorizationConsent true 是否要求用户手动确认授权
accessTokenTimeToLive 30 分钟 访问令牌有效期
refreshTokenTimeToLive 1 天 刷新令牌有效期
安全警告: 生产环境禁止使用 {noop} 明文密钥。必须通过 PasswordEncoder 使用 bcrypt 或 SCrypt 加密存储。同时,redirectUri 应使用 HTTPS 协议,防止中间人攻击。

4.4 授权服务器核心流程

下图展示了授权服务器在授权码模式下的核心交互流程,涵盖客户端注册、用户认证、令牌颁发三个关键阶段:

授权服务器核心流程(授权码模式) 客户端 授权服务器 资源所有者 1. 注册客户端 存储 client_id/secret 2. 请求授权码 验证客户端身份 3. 用户认证 输入凭证 4. 颁发 access_token 客户端注册在流程开始前完成,令牌颁发依赖已注册的 client_id 和 scope 白名单

4.5 多授权类型支持

消息队列场景通常需要同时支持 客户端凭证模式(服务间通信)和 授权码模式(用户代理交互)。注册客户端时需显式声明所有允许的授权类型:

  • CLIENT_CREDENTIALS:适用于后端服务直接调用,无需用户交互,适合消息队列生产者/消费者内部认证。
  • AUTHORIZATION_CODE:适用于需要用户授权的场景,如管理控制台触发消息发送。
  • REFRESH_TOKEN:必须与授权码或客户端凭证配合使用,用于无感续期令牌。
实践建议: 对于消息队列接口,建议将 CLIENT_CREDENTIALS 作为默认授权类型,减少交互延迟。仅在涉及用户数据操作时启用 AUTHORIZATION_CODE。

4.6 令牌存储与签名

授权服务器默认使用 JWT 格式的访问令牌,通过 com.nimbusds.jose 库实现签名与验证。生产环境应配置 RSA 密钥对,而非默认的 HMAC 密钥:

@Bean
public JWKSource<SecurityContext> jwkSource() {
    RSAKey rsaKey = Jwks.generateRsa();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

// 辅助类 Jwks 生成 RSA 密钥对
final class Jwks {
    static RSAKey generateRsa() {
        KeyPair keyPair = KeyGenerator.generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey)
            .privateKey(privateKey)
            .keyID(UUID.randomUUID().toString())
            .build();
    }
}

配置完成后,授权服务器将在 /.well-known/oauth-authorization-server 端点自动发布元数据,包括 JWKS URI、令牌端点、授权端点等,供客户端动态发现。