Próbuję uzyskać token okaziciela za pośrednictwem webclient z następującą konfiguracją dla testu integracji zabezpieczonego serwera zasobów w aplikacji serwletowej.

spring:
  security:
    oauth2:
      client:
        registration:
          idp:
            clientId: id
            clientSecret: secret
            authorization-grant-type: client_credentials
            scope: read
        provider:
          idp:
            authorization-uri: myidp/authorization.oauth2
            token-uri: myidp/token.oauth2
            user-info-uri: myidp/userinfo.openid
            user-name-attribute: name

I fasola,

    @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrations,
            OAuth2AuthorizedClientRepository authorizedClients) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                clientRegistrations, authorizedClients);
        // (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
        // oauth.setDefaultOAuth2AuthorizedClient(true);
        // (optional) set a default ClientRegistration.registrationId
        // oauth.setDefaultClientRegistrationId("client-registration-id");
        return WebClient.builder().apply(oauth.oauth2Configuration()).build();
    }

I automatyczne podłączenie klienta internetowego do testu i wywołanie go w ten sposób,

webClient.get().uri("http://localhost:" + port + "/web/it")
                .attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("idp")).retrieve()
                .bodyToMono(String.class).block();

Zakładam, że funkcja wymiany albo otrzyma token dostępu, jeśli jest dostępny, albo wykona wywołanie, aby uzyskać nowy od dostawcy tożsamości. Jednak zawsze zawiedzie, ponieważ HttpSessionOAuth2AuthorizedClientRepository, ponieważ HttpServletRequest ma wartość null.

Przy ogólnym teście wygląda to tak, że wprowadza to do automatycznej konfiguracji w celu skonfigurowania niektórych ziaren dla dostawcy IDP.

@SpringBootTest(classes = WebITApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension.class)
@ActiveProfiles("web-it")
class WebJwtIt {

    @LocalServerPort
    private int port;

    @Autowired
    private WebClient webClient;

    @Test
    void testIdpJwt() {

        String response = webClient.get().uri("http://localhost:" + port + "/web/it")
                .attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("ping")).retrieve()
                .bodyToMono(String.class).block();
        assertThat(response).isEqualTo("pass");
    }

    @RestController
    @SpringBootApplication
    @ImportAutoConfiguration(IdpAutoConfiguration.class)
    static class WebITApplication implements IdpSecurityAdapter {

              @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrations,
            OAuth2AuthorizedClientRepository authorizedClients) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                clientRegistrations, authorizedClients);
        // (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
        // oauth.setDefaultOAuth2AuthorizedClient(true);
        // (optional) set a default ClientRegistration.registrationId
        // oauth.setDefaultClientRegistrationId("client-registration-id");
        return WebClient.builder().apply(oauth.oauth2Configuration()).build();
    }
        public static void main(String args[]) {

            new SpringApplicationBuilder().profiles("web-it").build().run(WebITApplication.class, args);
        }

        @GetMapping
        public String secured() {
            return "secured";
        }

        @GetMapping("/web/it")
        public String securedOne() {
            return "pass";
        }

        @Override
        public void configure(final HttpSecurity httpSecurity) throws IdpSecurityAdapterException {
            try {
                httpSecurity.oauth2Client();
            } catch (Exception e) {
                throw new IdpSecurityAdapterException("Failed to Configure Oauth2Client", e);
            }
        }

        @Override
        public IdpProvider getIdpProvider() {
            return IdpProvider.MyIdp;
        }
    }

Czy mimo to klient sieciowy może pobrać dla mnie token i dodać go do żądania? Wiem, że było to możliwe dzięki spring-security-oauth:OAuthRestTemplate i czytając dokumentację pomyślałem, że jest to możliwe dzięki klientowi WWW.

3
Darren Forsythe 7 listopad 2018, 16:52

1 odpowiedź

Najlepsza odpowiedź

Problem polega na tym, że nie uruchamiasz swojego WebClienta we właściwy sposób.

Ponieważ jesteś po stronie klienta, nie masz dostępu do OAuth2AuthorizedClientRepository. Ten bean ma być połączony z serwerem zasobów, na którym logujesz się za pomocą deklaracji metody .oauth2Login() w konfiguracji HttpSecurity. Te szczegóły znajdziesz tutaj : Spring Security 5 Logowanie Oauth2.

Ponownie, jesteś po stronie klienta, więc potrzebujesz funkcji filtra wymiany, która wyzwoli żądanie do serwera autoryzacji w celu uzyskania tokena JWT. Zamiast tego możesz użyć ServerOAuth2AuthorizedClientExchangeFilterFunction.

Lepiej użyj WebClientCustomizer, aby dodać funkcję filtra wymiany w filtrze WebClient. Dlaczego ? Po prostu dlatego, że wstrzyknięcie do aplikacji Spring WebClient.Builder umożliwi Ci dostęp do natywnych metryk powiązanych z giełdami internetowymi.

Dlatego zbudujesz swojego WebClienta przy użyciu nowej instancji ziarna UnAuthenticatedServerOAuth2AuthorizedClientRepository w następujący sposób:

// Use injection to get an in-memory reposiroty or client registrations
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrations) {

    // Provides support for an unauthenticated user such as an application
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
            clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());

    // Build up a new WebClientCustomizer implementation to inject the oauth filter
    // function into the WebClient.Builder instance
    return new WebClientSecurityCustomizer(oauth);
}

Ponieważ jesteś po stronie klienta, nie przypisujesz użytkownika do swojego procesu, dlatego nie możesz użyć żadnej autoryzowanej instancji ziarna klienta repozytorium. Sprawdź dokumentację Spring Security: Dokumentacja Spring Security : Klasa UnAuthenticatedServerOAuth2AuthorizedClientRepository.

Próbowałem podsumować przypadek demonstracyjny w następującym projekcie GitHub: GitHub - Spring Security OAuth2 Machine- Scenariusz do maszyny.

Mam nadzieję, że dzięki temu uzyskasz więcej informacji na temat konfiguracji WebClient. Zapytaj, jeśli masz jakieś pytania.

12
Florent Gornes 4 grudzień 2018, 11:32
To jest niesamowite, do tej pory musiałem przegapić tę odpowiedź!
 – 
Darren Forsythe
20 grudzień 2018, 20:32
1
Po prostu chcę dodać dla wszystkich czytelników, jeśli jesteś w wątku w tle/wątku innym niż http w środowiskach serwletów stackoverflow.com/questions/55308918/… powinno to wyczyścić też. W Spring Security 5.2 dostępna jest odpowiednia konfiguracja.
 – 
Darren Forsythe
14 kwiecień 2019, 03:11
Mam problem z klientem WWW, pobiera token dostępu dla każdego żądania przed wywołaniem serwera zasobów. czy już to przetestowałeś?
 – 
Abdelhafid
28 styczeń 2020, 20:14
Bardzo miłe wyjaśnienie :)
 – 
Peter S.
23 grudzień 2020, 13:46