好的,以下是一篇关于Java与OpenID Connect/OAuth 2.1:实现安全的微服务间认证授权的技术讲座文章。
Java与OpenID Connect/OAuth 2.1:实现安全的微服务间认证授权
大家好!今天我们来探讨一个在微服务架构中至关重要的话题:如何使用Java与OpenID Connect (OIDC) 和 OAuth 2.1 实现安全的微服务间认证和授权。
一、微服务安全挑战与解决方案
在单体应用时代,安全性通常由应用服务器统一管理。但在微服务架构中,服务之间相互独立,直接暴露API给外部世界。这带来了以下挑战:
- 身份验证(Authentication): 如何验证访问服务的用户或服务的身份?
- 授权(Authorization): 如何确定已验证的用户或服务是否有权访问特定的资源?
- 凭证管理: 如何安全地存储和传递用户或服务的凭证?
- 安全审计: 如何追踪和审计对服务的访问行为?
OpenID Connect (OIDC) 和 OAuth 2.1 提供了解决这些问题的标准方法。
- OAuth 2.1: 是一个授权框架,允许第三方应用代表用户访问资源,无需共享用户的凭证。
- OpenID Connect (OIDC): 构建在 OAuth 2.1 之上,提供身份验证功能。它可以验证用户的身份,并获取关于用户的基本信息,例如姓名、电子邮件地址等。
二、OAuth 2.1 核心概念
理解 OAuth 2.1 的核心概念是至关重要的:
| 概念 | 描述 |
|---|---|
| Resource Owner | 拥有受保护资源的实体,通常是用户。 |
| Client | 代表资源所有者访问受保护资源的应用程序。 |
| Authorization Server | 负责验证资源所有者的身份,并颁发访问令牌。 |
| Resource Server | 托管受保护资源的服务器,它使用访问令牌来验证客户端的访问权限。 |
| Access Token | 客户端用来访问受保护资源的凭证。它代表资源所有者授予客户端的访问权限。 |
| Refresh Token | 客户端用来获取新的访问令牌的凭证,无需资源所有者再次授权。 |
| Scopes | 定义客户端可以访问的资源范围。 |
| Grant Types | 定义客户端如何获取访问令牌的方式。常见的授权类型包括授权码模式、密码模式、客户端凭据模式和隐式模式。OAuth2.1已移除隐式模式和密码模式,推荐使用授权码模式和客户端凭据模式。 |
三、OIDC 流程
OIDC流程主要包括以下步骤:
- 客户端发起授权请求: 客户端将用户重定向到授权服务器,请求授权。
- 用户认证和授权: 用户在授权服务器上进行身份验证,并授权客户端访问其资源。
- 授权服务器颁发授权码: 授权服务器颁发一个授权码给客户端。
- 客户端使用授权码获取访问令牌和ID令牌: 客户端将授权码发送到授权服务器,并获得访问令牌和ID令牌。
- 客户端使用访问令牌访问资源服务器: 客户端使用访问令牌访问资源服务器上的受保护资源。
- 资源服务器验证访问令牌: 资源服务器验证访问令牌的有效性,并授予客户端访问权限。
- 客户端使用ID令牌获取用户信息: 客户端可以使用ID令牌来获取有关已验证用户的基本信息。
四、Java 实现 OAuth 2.1 和 OIDC
现在我们来看如何使用 Java 实现 OAuth 2.1 和 OIDC。我们将使用 Spring Security OAuth2 框架来简化开发。
1. 引入依赖
首先,在 pom.xml 文件中添加 Spring Security OAuth2 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 配置 Resource Server
我们需要配置 Resource Server 来保护我们的微服务。这涉及到验证传入的访问令牌。
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.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public").permitAll() // 允许匿名访问
.anyRequest().authenticated() // 其他请求需要认证
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
// 这里可以配置 JWT 验证器,例如设置 JWKS URI
)
);
return http.build();
}
}
代码解释:
@EnableWebSecurity: 启用 Spring Security 的 Web 安全功能。SecurityFilterChain: 定义安全过滤器链。authorizeHttpRequests: 配置授权规则。requestMatchers("/public").permitAll(): 允许匿名访问/public路径。anyRequest().authenticated(): 所有其他请求都需要认证。
oauth2ResourceServer: 配置 OAuth 2.0 资源服务器。jwt: 配置 JWT (JSON Web Token) 支持。jwtAuthenticationConverter(): 可以自定义 JWT 认证转换器,将 JWT 声明转换为 Spring Security 的Authentication对象。jwkSetUri(): 设置 JWKS (JSON Web Key Set) URI,用于验证 JWT 签名。
3. 配置 Authorization Server (示例使用 Spring Authorization Server)
Spring Authorization Server 是一个开源的 OAuth 2.1 和 OIDC 授权服务器实现。
引入依赖:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>1.2.0</version> <!-- 替换为最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
配置:
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.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.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.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import java.util.UUID;
@Configuration
public class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.build();
}
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret") // 生产环境替换为安全的存储方式
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") // 替换为你的客户端回调地址
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
.clientSettings(clientSettings -> clientSettings.requireUserConsent(false)) // 是否需要用户确认授权
.build();
// Save registered client in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(registeredClient);
return registeredClientRepository;
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().issuer("http://auth-server:9000").build();
}
}
代码解释:
authorizationServerSecurityFilterChain: 配置授权服务器的安全过滤器链。RegisteredClientRepository: 用于管理已注册的客户端。这里使用JdbcRegisteredClientRepository将客户端信息存储在数据库中。clientId: 客户端的 ID。clientSecret: 客户端的密钥。注意:在生产环境中,不要使用{noop}前缀,应该使用加密存储的密钥。clientAuthenticationMethod: 客户端的身份验证方法。CLIENT_SECRET_BASIC表示使用 HTTP Basic 认证。authorizationGrantType: 客户端支持的授权类型。redirectUri: 授权服务器将用户重定向回客户端的 URI。scope: 客户端请求的权限范围。clientSettings: 客户端设置。requireUserConsent(false): 是否需要用户确认授权。
ProviderSettings: 配置授权服务器的提供者设置。issuer: 授权服务器的 issuer URI。
4. 配置客户端
客户端需要配置 OAuth2 客户端属性,以便与授权服务器进行交互。可以使用 Spring Boot 的 application.yml 或 application.properties 文件进行配置。
spring:
security:
oauth2:
client:
registration:
messaging-client-oidc:
client-id: messaging-client
client-secret: secret
client-authentication-method: client_secret_basic
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc"
scope: openid, message.read, message.write
provider:
oidc-provider:
issuer-uri: http://auth-server:9000
server:
port: 8080
代码解释:
spring.security.oauth2.client.registration: 配置客户端注册信息。client-id: 客户端的 ID,与授权服务器注册的客户端 ID 匹配。client-secret: 客户端的密钥,与授权服务器注册的客户端密钥匹配。client-authentication-method: 客户端的身份验证方法。authorization-grant-type: 客户端使用的授权类型。redirect-uri: 授权服务器将用户重定向回客户端的 URI。scope: 客户端请求的权限范围。
spring.security.oauth2.client.provider: 配置 OAuth2 提供者信息。issuer-uri: 授权服务器的 issuer URI。
5. 创建Controller
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MessageController {
@GetMapping("/messages")
public String getMessage(Authentication authentication) {
return "Message from resource server. User: " + authentication.getName();
}
@GetMapping("/public")
public String getPublicMessage() {
return "Public message from resource server.";
}
}
代码解释:
/messages:受保护的API,只有经过身份验证的用户才能访问。/public:公共API,允许匿名访问。
五、微服务间的认证和授权
以上展示了用户如何通过 OIDC 进行认证,以及客户端如何使用 OAuth 2.1 访问资源。现在让我们看看如何实现微服务间的认证和授权。
1. 使用 Client Credentials Grant
当一个微服务需要代表自身而不是用户访问另一个微服务时,可以使用 Client Credentials Grant。
- 客户端微服务: 使用其客户端 ID 和密钥向授权服务器请求访问令牌。
- 授权服务器: 验证客户端的身份,并颁发访问令牌。
- 客户端微服务: 使用访问令牌访问目标微服务。
2. 配置客户端微服务
在客户端微服务的 application.yml 或 application.properties 中配置 Client Credentials Grant:
spring:
security:
oauth2:
client:
registration:
internal-client: # 内部客户端的名称
client-id: internal-service-client # 客户端ID
client-secret: internal-service-secret # 客户端密钥
client-authentication-method: client_secret_basic
authorization-grant-type: client_credentials
scope: message.read # 客户端请求的权限范围
provider:
internal-provider:
issuer-uri: http://auth-server:9000 # 授权服务器的issuer URI
3. 获取访问令牌
使用 OAuth2AuthorizedClientManager 获取访问令牌。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
@Service
public class InternalServiceClient {
@Autowired
private OAuth2AuthorizedClientService authorizedClientService;
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Autowired
private OAuth2AuthorizedClientManager authorizedClientManager;
public String getMessageFromResourceServer() {
ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("internal-client");
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("internal-client")
.principal("internal-service") // 设置principal
.build();
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest);
if (authorizedClient == null) {
throw new RuntimeException("Failed to authorize client.");
}
return WebClient.builder()
.baseUrl("http://resource-server:8080") // 替换为资源服务器的地址
.defaultHeader("Authorization", "Bearer " + authorizedClient.getAccessToken().getTokenValue())
.build()
.get()
.uri("/messages")
.retrieve()
.bodyToMono(String.class)
.block();
}
}
代码解释:
ClientRegistrationRepository: 用于获取客户端注册信息。OAuth2AuthorizedClientService: 用于管理已授权的客户端。OAuth2AuthorizedClientManager: 用于授权客户端并获取访问令牌。authorizeRequest: 创建 OAuth2 授权请求,指定客户端注册 ID。authorizedClientManager.authorize(authorizeRequest): 授权客户端并获取访问令牌。WebClient: 用于发送 HTTP 请求。defaultHeader("Authorization", "Bearer " + authorizedClient.getAccessToken().getTokenValue()): 设置授权头,包含访问令牌。
4. 目标微服务
目标微服务需要像之前一样配置为 Resource Server,验证传入的访问令牌。
六、安全注意事项
- 密钥管理: 安全地存储和管理客户端密钥,避免硬编码在代码中。使用 Vault、AWS Secrets Manager 等密钥管理工具。
- 传输安全: 使用 HTTPS 协议进行所有通信,确保数据在传输过程中得到加密。
- 输入验证: 验证所有输入数据,防止注入攻击。
- 最小权限原则: 授予客户端最小必要的权限范围。
- 定期审计: 定期审计安全配置和访问日志,及时发现和解决安全问题。
- Token过期时间: 设置合理的Token过期时间,避免Token被长期滥用。
七、总结
本次讲座我们深入探讨了如何使用Java与OpenID Connect和OAuth 2.1实现微服务间的安全认证和授权。正确实施这些技术能够确保你的微服务架构安全可靠,为用户提供更安全的体验。