tux*_*uxy 6 java spring oauth-2.0 spring-boot
我正在尝试创建一个 Spring Boot REST 应用程序,该应用程序必须对另一个受 OAuth2 保护的应用程序进行远程 REST 调用。
第一个应用程序使用 Reactive WebClient 来调用第二个 OAuth2 REST 应用程序。我已经配置了WebClient
grant_type“client_credentials”。
应用程序.yml
spring:
security:
oauth2:
client:
provider:
client-registration-id:
token-uri: http://localhost:8080/oauth/token
registration:
client-registration-id:
authorization-grant-type: client_credentials
client-id: public
client-secret: test
client-authentication-method: post
scope: myscope
Run Code Online (Sandbox Code Playgroud)
@Configuration
public class WebClientConfig {
@Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId("client-registration-id");
return WebClient.builder().filter(oauth).build();
}
}
Run Code Online (Sandbox Code Playgroud)
@Component
public class WebClientChronJob {
Logger logger = LoggerFactory.getLogger(WebClientChronJob.class);
@Autowired
private WebClient webClient;
@Scheduled(fixedRate = 5000)
public void logResourceServiceResponse() {
webClient.get()
.uri("http://localhost:8080/test")
.retrieve()
.bodyToMono(String.class)
.map(string -> "RESPONSE: " + string)
.subscribe(logger::info);
}
}
Run Code Online (Sandbox Code Playgroud)
根据Baeldung Spring Webclient Oauth2上的文章,第二次WebClientChronJob
运行时,应用程序应该请求资源,而无需首先请求令牌,因为最后一个令牌尚未过期。不幸的是,启用调试日志后,我注意到相反的情况:每次作业请求资源时,它都会要求一个新令牌。如果配置或代码中缺少某些内容,请告诉我。
Netty started on port(s): 8082
Started MyApp in 2.242 seconds (JVM running for 2.717)
HTTP POST http://localhost:8080/oauth/token
Writing form fields [grant_type, scope, client_id, client_secret] (content masked)
Response 200 OK
Decoded [{access_token=nrLr7bHpV0aqr5cQNhv0NjJYvVv3bv, token_type=Bearer, expires_in=86400, scope=rw:profile (truncated)...]
Cancel signal (to close connection)
HTTP GET http://localhost:8080/test
Response 200 OK
Decoded "{"status":{"description":"ok","success":true},"result":[]}"
ESPONSE: {"status":{"description":"ok","success":true},"result":[]}
HTTP POST http://localhost:8080/oauth/token
Writing form fields [grant_type, scope, client_id, client_secret] (content masked)
Response 200 OK
Decoded [{access_token=CsOxziw6W6J7IoqA8EiF4clhiwVJ8m, token_type=Bearer, expires_in=86400, scope=rw:profile (truncated)...]
Cancel signal (to close connection)
HTTP GET http://localhost:8080/test
Response 200 OK
Decoded "{"status":{"description":"ok","success":true},"result":[]}"
ESPONSE: {"status":{"description":"ok","success":true},"result":[]}
Run Code Online (Sandbox Code Playgroud)
下面是我在 pom.xml 中唯一的依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Run Code Online (Sandbox Code Playgroud)
Spring 继续前进,由于 @angus要求使用现已弃用的替代方法UnAuthenticatedServerOAuth2AuthorizedClientRepository
,我想分享我的实现。这是分别使用Spring Boot 2.4.4
和Spring Security 5.4.5
。
推荐的替代方案是UnAuthenticatedServerOAuth2AuthorizedClientRepository
. AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
另外,向您的 bean提供 的推荐方法WebClient
是注入WebClient.Builder
. 所以,WebClient.Builder
像这样配置你的:
@Configuration
public class OAuth2ClientConfiguration {
@Bean
public WebClientCustomizer oauth2WebClientCustomizer(
ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oAuth2AuthorizedClientExchangeFilterFunction =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(reactiveOAuth2AuthorizedClientManager);
oAuth2AuthorizedClientExchangeFilterFunction.setDefaultClientRegistrationId("api-client");
return webClientBuilder ->
webClientBuilder
.filter(oAuth2AuthorizedClientExchangeFilterFunction);
}
@Bean
public ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager(
ReactiveClientRegistrationRepository registrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
registrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(
new ClientCredentialsReactiveOAuth2AuthorizedClientProvider());
return authorizedClientManager;
}
}
Run Code Online (Sandbox Code Playgroud)
这就是全部,真的。只要授权令牌对于资源请求有效,就会只获取一次。
稍微偏离主题:如果您想防止在集成测试中完全发生对令牌 URI 的授权请求,您可能对此感兴趣。
下面是一个相应的src/test/resources/application.yml
集成测试,使用MockServer来模拟资源服务器和授权服务器,以证明对于多个资源请求,Token URI 被调用一次。
spring:
security:
oauth2:
client:
registration:
api-client:
authorization-grant-type: client_credentials
client-id: test-client
client-secret: 6b30087f-65e2-4d89-a69e-08cb3c9f34d2
provider: some-keycloak
provider:
some-keycloak:
token-uri: http://localhost:1234/token/uri
api:
base-url: http://localhost:1234/api/v1
Run Code Online (Sandbox Code Playgroud)
@SpringBootTest
@ExtendWith(MockServerExtension.class)
@MockServerSettings(ports = 1234)
class TheRestClientImplIT {
@Autowired
TheRestClient theRestClient;
@BeforeEach
void setUpTest(MockServerClient mockServer) {
mockServer
.when(HttpRequest
.request("/token/uri"))
.respond(HttpResponse
.response("{\n" +
" \"access_token\": \"c29tZS10b2tlbg==\",\n" +
" \"expires_in\": 300,\n" +
" \"token_type\": \"bearer\",\n" +
" \"not-before-policy\": 0,\n" +
" \"session_state\": \"7502cf31-b210-4754-b919-07e1d8493fa3\"\n" +
"}")
.withContentType(MediaType.APPLICATION_JSON));
mockServer
.when(HttpRequest
.request("/api/v1/some-resource")
.withHeader("Authorization", "Bearer c29tZS10b2tlbg=="))
.respond(HttpResponse
.response("Hello from resource!"));
}
@Test
void should_access_protected_resource_more_than_once_but_request_a_token_exactly_once(MockServerClient mockServer) {
int resourceRequestCount = 2; // how often should the resource be requested?
Stream
.iterate(1, i -> ++i)
.limit(resourceRequestCount)
.forEach(i -> {
LoggerFactory
.getLogger(TheRestClientImplIT.class)
.info("Performing request number: {}", i);
StepVerifier
.create(theRestClient.getResource())
.assertNext(response -> {
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo("Hello from resource!");
})
.verifyComplete();
});
// verify token request happened exactly once
mockServer.verify(HttpRequest
.request("/token/uri"),
VerificationTimes.once());
// verify resource request happened as often as defined
mockServer.verify(HttpRequest
.request("/api/v1/some-resource")
.withHeader("Authorization", "Bearer c29tZS10b2tlbg=="),
VerificationTimes.exactly(resourceRequestCount));
}
}
Run Code Online (Sandbox Code Playgroud)
作为参考,这是TheRestClient
实现:
@Component
public class TheRestClientImpl implements TheRestClient {
private final WebClient webClient;
@Autowired
public TheRestClientImpl(WebClient.Builder webClientBuilder,
@Value("${api.base-url}") String apiBaseUrl) {
this.webClient = webClientBuilder
.baseUrl(apiBaseUrl)
.build();
}
@Override
public Mono<ResponseEntity<String>> getResource() {
return webClient
.get()
.uri("/some-resource")
.retrieve()
.toEntity(String.class);
}
}
Run Code Online (Sandbox Code Playgroud)
我找到了解决我的问题的方法。Spring Security 版本 5.1.x 的 WebClient 当前实现在令牌过期后不会请求新令牌,并且可能 Spring 开发人员决定每次都请求令牌。Spring 的开发人员还决定仅在新版本 5.2.0.M2 或 (M1) 中修复此错误,而不将修复向后移植到 5.1.x
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<groupId>net.tuxy</groupId>
<artifactId>oauth2-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MyApp</name>
<description>Spring Boot WebClient OAuth2 client_credentials example</description>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.2.0.M2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.2.0.M2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.2.0.M2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-core</artifactId>
<version>5.2.0.M2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.2.0.M2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
9874 次 |
最近记录: |