[译]Spring Boot授权服务器 - 使用 Java 的资源服务器和客户端凭证示例
概述
在本文中,我们将创建一个授权服务器,为任何客户端生成 access_token。这称为 OAuth2 的 client_credentials
流程。它主要用于服务间通信。
我们将使用 spring boot oauth2 授权服务器依赖项来创建身份验证服务器。我们还将创建一个资源服务器和客户端来对其进行端到端测试。
Spring 授权服务器
我们首先创建授权服务器。
依赖项:
让我们将以下依赖项添加到我们的项目中。
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.0.0'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
我们正在使用 spring oauth2 依赖项的最新(当时)稳定版本。
Java 实现:
让我们创建一个名为 AuthorizationServerConfig 的配置类,并向该类添加 @Configuration 注解。现在让我们创建以下 bean 来完成配置:
- SecurityFilterChain
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.build();
}
我们将把 bean 的顺序设置为最高,因为我们想首先执行它。
- RegisteredClientRepository
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("oauth-client")
.clientSecret("{noop}oauth-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
现在让我们使用内存存储库对内容进行硬编码。我们可以根据我们的需要更新这些。
- JwtDecoder
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
我们将使用它来解码令牌以进行验证。
- JWKSource
@Bean
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() throws NoSuchAlgorithmException {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
我们在解码器 bean 中使用这个源,所以我们需要定义它。我们使用 RSA 2048 密钥对,我们也可以在需要时更改它。
- AuthorizationServerSettings
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
现在我们已经配置了一切,让我们尝试运行应用程序并获取令牌:
curl -X POST 'http://localhost:9090/oauth2/token?grant_type=client_credentials' \
--header 'Authorization: Basic b2F1dGgtY2xpZW50Om9hdXRoLXNlY3JldA=='
注意:根据您的配置更新端口号。
它应该给出如下响应:
{
"access_token": "eyJraWQiOiJiYWM0ZmMxYS02MGJiLTQ0ZTAtODU4MC1iNzcwYWU2MjkwZWEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJvYXV0aC1jbGllbnQiLCJhdWQiOiJvYXV0aC1jbGllbnQiLCJuYmYiOjE2NzQ5ODYzNjcsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6OTA5MCIsImV4cCI6MTY3NDk4NjY2NywiaWF0IjoxNjc0OTg2MzY3fQ.DxiIbV7jdRnW15WnnqcjFCLyfXmrU_trl1M3nxej_nIWK60Jx9Vm4HzpxBJugemhrMg-qizQ03TTNswfL9AgTIsLeh_D8TDjcQJy6XFWgElxfUYqUFeZmlXPmQKFmmPyIChlSAFbX1L8QvcgFE1c8GHC900RiKVgGLhT5MOZx5l1WBCbNQ_Rv2u9utcz7EqYTb0y_PjD4EC8UaGdGGlqvEAnKvRVIhxRqFarqh-OW4oUfwfwu1xQIvyWphSDegcOjIERFkhVcQeKO-a3zZS9sfJ03ppZhzAsa5O-qswtbzThO9SWQg7JUgyo7qd-zHIRhwPtEWxDGaBt2QGo7jjopw",
"token_type": "Bearer",
"expires_in": 299
}
Spring 资源服务器
现在让我们创建一个受此身份验证服务器保护的 API 端点,其范围为我们在令牌创建中使用的 articles.read。
依赖项:
让我们将以下依赖项添加到我们的项目中:
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Java 实现:
让我们首先创建一个简单的 rest 控制器,然后创建一个配置,以在正确的范围内保护该 API。之后,我们将在 application.yml 文件中配置身份验证服务器设置。
- API 控制器
@RestController
public class ArticlesController {
@GetMapping("/articles")
public String[] getArticles() {
return new String[] { "Article 1", "Article 2", "Article 3" };
}
}
我们创建了一个简单的 GET API 端点 /articles,它将返回文章列表。
- ResourceServerConfig
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/articles/**")
.access("hasAuthority('SCOPE_articles.read')")
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
我们将创建一个配置类并使用@EnableWebSecurity 对其进行注释。我们将创建一个 SecurityFilterChain 的 bean,在其中定义 API 和所需的范围。
- application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:9090
我们在这里定义 oauth2 配置,注意将 issuer-url 的端口更新为正确的端口。
现在一切都已配置完毕,让我们启动该服务并向 API 发出带有或不带有令牌的请求。您应该得到一个没有令牌或带有无效令牌的 401 响应,并且您应该得到带有有效令牌的正确响应。
客户端服务器
我们现在将创建一个简单的 Spring Boot 项目,它将使用资源服务器创建的 API。我们将在此处配置身份验证服务器详细信息,以便它在发出 API 请求之前自动获取令牌。
依赖项:
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework:spring-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Java 实现:
我们首先创建配置类,然后创建一个测试 API 来向资源服务器发出请求。之后,我们将在 application.yml 文件中定义令牌配置。
- **SecurityConfig **
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.oauth2Client().and().build();
}
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> tokenResponseClient) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(r -> r.accessTokenResponseClient(tokenResponseClient)).clientCredentials().build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, oAuth2AuthorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> tokenResponseClient() {
return new DefaultClientCredentialsTokenResponseClient();
}
}
- application.yml
spring:
security:
oauth2:
client:
registration:
articles-client:
client-id: oauth-client
client-secret: oauth-secret
authorization-grant-type: client_credentials
scope: articles.read
client-name: spring-client
provider:
articles-client:
token-uri: http://localhost:9090/oauth2/token
- 客户端 API(向资源服务器发出请求)
@RestController
public class ArticlesController {
@Autowired
private WebClient webClient;
@GetMapping(value = "/test")
public String[] test() {
return this.webClient
.get()
.uri("http://127.0.0.1:9091/articles")
.attributes(clientRegistrationId("articles-client"))
.retrieve()
.bodyToMono(String[].class)
.block();
}
}
我们可以在这里看到,当我们调用 /test API 时,它会从我们的身份验证服务器获取令牌,然后向我们的资源服务器 /articles 端点发出请求并返回响应。
让我们运行所有三个服务器并向客户端服务器发出请求,它应该返回正确的响应。请注意更新所有位置的端口号。在示例中,我使用了以下端口:
- 9090: auth-server
- 9091: resource-server
- 9092: client-server
结论
在本文中,我们学习了如何使用 Spring Boot 创建授权服务器以及如何在资源服务器和客户端服务器中配置它。
您可以在此 GitHub 存储库中找到此 示例的代码。
原文链接:https://blog.devgenius.io/spring-boot-authorization-server-825230ae0ed2