简体   繁体   English

Spring 安全 - 使用 WebClient 访问通过 Oauth2“密码”授权类型保护的资源

[英]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.

相关问题 Spring Security 5 OAuth2 客户端密码授予类型 - Spring Security 5 OAuth2 client password grant type 春季安全oauth2:grant_type = password身份验证 - Spring security oauth2 : grant_type=password Authentication oAuth2客户端在Spring Security中使用密码授予 - oAuth2 client with password grant in Spring Security 在Spring Security OAuth2中使用用户名密码授予中的刷新令牌请求新的访问令牌 - Request new access token using refresh token in username-password grant in Spring Security OAuth2 Spring Security OAuth2受保护的资源实际上未被保护…过滤器不起作用? - Spring Security OAuth2 Protected Resource not actually protected… Filters Not Working? Spring 安全性 OAuth 具有密码授权类型的 WebClient 客户端不要求新令牌 - Spring Security OAuth Client for WebClient With Password Grant Type Doesn't Ask For New Token Spring Boot Oauth2验证用于资源所有者密码凭证授予的访问令牌 - Spring Boot Oauth2 Validating Access Token for Resource Owner Password Credentials Grant 使用Spring Security针对REST服务器为Web Client授予Oauth2密码 - Oauth2 password grant for Web Client against REST server using spring security 无需访问令牌即可访问受弹簧oauth2保护的资源 - Can access spring oauth2 protected resource without access token Spring Security和OAuth2使用自定义授权类型生成令牌 - Spring Security and OAuth2 generate token with custom grant type
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM