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 授权服务器核心流程
下图展示了授权服务器在授权码模式下的核心交互流程,涵盖客户端注册、用户认证、令牌颁发三个关键阶段:
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、令牌端点、授权端点等,供客户端动态发现。