[英]Spring security - using WebClient access a resource that is protected via Oauth2 "Password" grant type
How can I access with WebClient a resource that is protected via the Oauth2 'Password' grant type?如何使用 WebClient 访问受 Oauth2“密码”授权类型保护的资源?
Connecting with Oauth2 'client-credentials' works.连接 Oauth2 'client-credentials' 有效。 In this case I need the password grant type.
在这种情况下,我需要密码授予类型。
I get this error:我收到此错误:
401 Unauthorized from GET http://localhost:8086/test2 at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198) ~[spring-webflux-5.3.19.jar:5.3.19]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ 401 from GET http://localhost:8086/test2
I configured the auth server via Keycloack with Access Type 'public'.我通过 Keycloack 配置了 auth 服务器,访问类型为“public”。 I checked accessing a token via Postman. You can find more details via this post .
我检查了通过 Postman 访问令牌。您可以通过这篇文章找到更多详细信息。
The Websecurity config (working for grant-type client-credentials): Websecurity 配置(适用于授权类型的客户端凭证):
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("*").permitAll();
}
}
The webclient is created as a Bean. webclient 被创建为一个 Bean。 It works for the client-credentials grant type.
它适用于 client-credentials 授予类型。
@Configuration
public class WebClientOAuth2Config {
@Bean("method2")
WebClient webClientGrantPassword( @Qualifier("authclientmgr2") OAuth2AuthorizedClientManager authorizedClientManager2) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientManager2);
oauth2Client2.setDefaultClientRegistrationId("businesspartners");
return WebClient.builder().apply(oauth2Client2.oauth2Configuration()).build();
}
@Bean("authclientmgr2")
public OAuth2AuthorizedClientManager authorizedClientManager2(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
The controller accessing the resource server:访问资源服务器的controller:
@RestController
public class Test2Controller {
@Autowired
private @Qualifier("method2") WebClient webClient2;
@GetMapping("/test2")
public String test2() {
return webClient2.get().uri("http://localhost:8086/test2")
.attributes(clientRegistrationId("businesspartners"))
.retrieve().bodyToMono(String.class).block();
}
}
The application.yml config is: application.yml 配置是:
server:
port: 8081
spring:
security:
oauth2:
client:
registration:
businesspartners:
client-id: myclient2
authorization-grant-type: password
client-name: johan
client-secret: password
provider:
businesspartners:
issuer-uri: http://localhost:28080/auth/realms/realm2
token-uri: http://localhost:28080/auth/realms/realm2/protocol/openid-connect/token
The maven dependencies include: maven 依赖包括:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Not sure whether it's possible to do it using application.yml
but here is how you can configure it in code不确定是否可以使用
application.yml
来完成,但这里是你如何在代码中配置它
private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(
String clientRegistrationId, SecurityConfig config) {
var clientRegistration = ClientRegistration
.withRegistrationId(clientRegistrationId)
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.tokenUri(config.getTokenUri() + "/token")
.clientId(config.getClientId())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build();
var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);
var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
authRepository, authClientService);
var clientAuthProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
authClientManager.setAuthorizedClientProvider(clientAuthProvider);
authClientManager.setContextAttributesMapper(authorizeRequest -> Mono.just(
Map.of(
OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, config.getUsername(),
OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, config.getPassword()
)
));
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultClientRegistrationId(clientRegistrationId);
return oauth;
}
and then use in in WebClient
然后在
WebClient
中使用
WebClient webClient = WebClient.builder()
.filter(oauth("businesspartners", securityConfig))
.build();
where SecurityConfig
is defined like SecurityConfig
的定义如下
@lombok.Value
@lombok.Builder
static class SecurityConfig {
String tokenUri;
String clientId;
String username;
String password;
}
Here is a full test using WireMock
这是使用
WireMock
的完整测试
@Slf4j
@SpringBootTest(webEnvironment = NONE)
@AutoConfigureWireMock(port = 0) // random port
class WebClientTest {
@Value("${wiremock.server.port}")
private int wireMockPort;
@Test
void authClientTest() {
String authResponse = """
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "token",
"refresh_token": "token"
}""";
stubFor(post(urlPathMatching("/token"))
.withRequestBody(
containing("client_id=myclient2")
.and(containing("grant_type=password"))
.and(containing("password=password"))
.and(containing("username=username"))
)
.withHeader(HttpHeaders.CONTENT_TYPE, containing(MediaType.APPLICATION_FORM_URLENCODED.toString()))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody(authResponse)
)
);
stubFor(get(urlPathMatching("/test"))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody("{}")
)
);
SecurityConfig config = SecurityConfig.builder()
.tokenUri("http://localhost:" + wireMockPort)
.clientId("myclient2")
.username("username")
.password("password")
.build();
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:" + wireMockPort)
.filter(oauth("test", config))
.build();
Mono<String> request = webClient.get()
.uri("/test")
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(request)
.assertNext(res -> log.info("response: {}", res))
.verifyComplete();
}
private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(
String clientRegistrationId, SecurityConfig config) {
var clientRegistration = ClientRegistration
.withRegistrationId(clientRegistrationId)
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.tokenUri(config.getTokenUri() + "/token")
.clientId(config.getClientId())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build();
var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);
var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
authRepository, authClientService);
var clientAuthProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
authClientManager.setAuthorizedClientProvider(clientAuthProvider);
authClientManager.setContextAttributesMapper(authorizeRequest -> Mono.just(
Map.of(
OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, config.getUsername(),
OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, config.getPassword()
)
));
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultClientRegistrationId(clientRegistrationId);
return oauth;
}
@lombok.Value
@lombok.Builder
static class SecurityConfig {
String tokenUri;
String clientId;
String username;
String password;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.