programing

OAuth2RestTemplate용 Spring Security 5 교체

lastcode 2023. 4. 2. 10:36
반응형

OAuth2RestTemplate용 Spring Security 5 교체

»spring-security-oauth2:2.4.0.RELEASE「」의 OAuth2RestTemplate,OAuth2ProtectedResourceDetails ★★★★★★★★★★★★★★★★★」ClientCredentialsAccessTokenProvider모두 권장되지 않는 것으로 표시되었습니다.

이러한 클래스의 javadoc에서 봄철 보안 이행 가이드를 소개합니다.이 가이드에서는 스프링 보안의 핵심인 5프로젝트로 이행할 필요가 있음을 시사합니다.그러나 이 프로젝트에서 사용 사례를 어떻게 구현해야 할지 고민하고 있습니다.

애플리케이션으로의 착신 요구를 인증하고, 서드파티제의 OAuth 프로바이더를 사용해 ID를 검증하는 경우, 모든 문서와 예에서는, 제3 파트 OAuth 프로바이더와의 통합에 대해 설명하고 있습니다.

에서는, 「」을 「요청」으로 것입니다.RestTemplateOAuth의 경우현재 작성 중입니다.OAuth2ProtectedResourceDetails로 넘깁니다.OAuth2RestTemplate 요.ClientCredentialsAccessTokenProviderOAuth2ResTemplate사용 중인 OAuth 프로바이더에 필요한 토큰 요청에 헤더를 추가할 뿐입니다.

spring-security 5 문서에서는 토큰 요구의 커스터마이즈에 대해 언급하고 있습니다만, 서드파티제의 OAuth 프로바이더에 의한 착신 요구의 인증에 관한 섹션을 발견했습니다.이것을 어떻게 조합해 사용하는지는 불명확합니다.ClientHttpRequestInterceptor외부 서비스에 대한 각 발신 요구가 처음에 토큰을 얻은 후 요청에 추가되도록 합니다.

위의 에는 「」에 되어 있습니다.OAuth2AuthorizedClientService, 도 요격에 것 .ClientRegistrationRepository서드파티 프로바이더의 등록을 보관 유지하고 있는 것 같습니다.이것에 의해, 착신 요구가 확실히 인증되고 있는 것을 확인할 수 있습니다.

Spring-security 5의 신기능을 이용하여 OAuth 프로바이더를 등록하고 어플리케이션에서 발신요구에 추가할 토큰을 얻을 수 있는 방법이 있습니까?

5의2.0 은 Spring Security 5.2.x 의 OAuth 2.0 을 지원하지 .RestTemplate단, ,만,WebClient스프링 보안 레퍼런스:

HTTP 클라이언트 지원

  • WebClient서블릿 (서브릿)

외에 '있다'도 있어요.RestTemplate는 이후 버전에서 더 이상 사용되지 않습니다.RestTemplate javadoc 참조:

메모: 5.0 이후로는 논블로킹, 리액티브org.springframework.web.reactive.client.WebClient 대체품을 RestTemplate동기 및 비동기 및 스트리밍 시나리오를 효율적으로 지원합니다.RestTemplate는 향후 버전에서 폐지될 예정이며 향후 주요 신기능은 추가되지 않을 예정입니다.「 」를 .WebClient스프링 프레임워크

따라서, 최선의 해결방법은 포기해야 합니다.RestTemplateWebClient.


「」를 사용합니다.WebClient Credentials Flow의 경우 credential의 경우

클라이언트 등록 및 프로바이더를 프로그래밍 방식으로 설정하거나 Spring Boot 자동 설정을 사용하여 설정합니다.

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: clientId
            client-secret: clientSecret
            authorization-grant-type: client_credentials
        provider:
          custom:
            token-uri: http://localhost:8081/oauth/token

OAuth2AuthorizedClientManager @Bean:

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                    .clientCredentials()
                    .build();

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}

설정합니다.WebClient하여 ""ServerOAuth2AuthorizedClientExchangeFilterFunction의 「」를 해 주세요.OAuth2AuthorizedClientManager:

@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("custom");
    return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();
}

.WebClient 내에 을 포함합니다.

안녕하세요. RestTemplate는 Spring Security 5에서 아직 지원되고 있습니다.RestTemplate는 비반응형 어플리케이션입니다.이행가이드에 기재된 바와 같이 스프링 보안을 적절하게 구성하고 대행 수신기를 작성하기만 하면 됩니다.

client_credentials 플로우를 사용하려면 다음 구성을 사용합니다.

application.yml

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
      client:
        registration:
          okta:
            client-id: ${okta.oauth2.clientId}
            client-secret: ${okta.oauth2.clientSecret}
            scope: "custom-scope"
            authorization-grant-type: client_credentials
            provider: okta
        provider:
          okta:
            authorization-uri: ${okta.oauth2.issuer}/v1/authorize
            token-uri: ${okta.oauth2.issuer}/v1/token

OauthResTemplate 설정

@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig {

    public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";

    private final RestTemplateBuilder restTemplateBuilder;
    private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ClientRegistrationRepository clientRegistrationRepository;

    @Bean(OAUTH_WEBCLIENT)
    RestTemplate oAuthRestTemplate() {
        var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);

        return restTemplateBuilder
                .additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
                .setReadTimeout(Duration.ofSeconds(5))
                .setConnectTimeout(Duration.ofSeconds(1))
                .build();
    }

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager() {
        var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

}

가로채기

public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor {

    private final OAuth2AuthorizedClientManager manager;
    private final Authentication principal;
    private final ClientRegistration clientRegistration;

    public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
        this.manager = manager;
        this.clientRegistration = clientRegistration;
        this.principal = createPrincipal();
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
                .withClientRegistrationId(clientRegistration.getRegistrationId())
                .principal(principal)
                .build();
        OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
        if (isNull(client)) {
            throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
        }

        request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
        return execution.execute(request, body);
    }

    private Authentication createPrincipal() {
        return new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return Collections.emptySet();
            }

            @Override
            public Object getCredentials() {
                return null;
            }

            @Override
            public Object getDetails() {
                return null;
            }

            @Override
            public Object getPrincipal() {
                return this;
            }

            @Override
            public boolean isAuthenticated() {
                return false;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
            }

            @Override
            public String getName() {
                return clientRegistration.getClientId();
            }
        };
    }
}

그러면 첫 번째 콜 및 토큰 기한이 만료될 때마다 access_token이 생성됩니다.OAuth2 Authorized Client Manager가 이 모든 것을 관리합니다.

나는 @matt Williams의 대답이 꽤 도움이 된다는 것을 알았다.WebClient 설정을 위해 clientId와 secret을 프로그램적으로 전달하고 싶은 경우를 위해 추가하고 싶습니다.방법은 다음과 같습니다.

 @Configuration
    public class WebClientConfig {

    public static final String TEST_REGISTRATION_ID = "test-client";

    @Bean
    public ReactiveClientRegistrationRepository clientRegistrationRepository() {
        var clientRegistration = ClientRegistration.withRegistrationId(TEST_REGISTRATION_ID)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .clientId("<client_id>")
                .clientSecret("<client_secret>")
                .tokenUri("<token_uri>")
                .build();
        return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
    }

    @Bean
    public WebClient testWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) {

        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,  new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
        oauth.setDefaultClientRegistrationId(TEST_REGISTRATION_ID);

        return WebClient.builder()
                .baseUrl("https://.test.com")
                .filter(oauth)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    }
}

위의 답변은 @Anar Saltanov의 도움을 받아 이 지경에 이르렀지만 OAuth 토큰 요청에 헤더를 추가해야 했기 때문에 사용 사례의 문제를 어떻게 해결했는지에 대한 완전한 답변을 드리려고 생각했습니다.

공급자 세부 정보 구성

음음에 다음 합니다.application.properties

spring.security.oauth2.client.registration.uaa.client-id=${CLIENT_ID:}
spring.security.oauth2.client.registration.uaa.client-secret=${CLIENT_SECRET:}
spring.security.oauth2.client.registration.uaa.scope=${SCOPE:}
spring.security.oauth2.client.registration.uaa.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.uaa.token-uri=${UAA_URL:}

커스텀을 ReactiveOAuth2AccessTokenResponseClient

에, 「서버간 통신」을 .ServerOAuth2AuthorizedClientExchangeFilterFunction은, 「」만을 .ReactiveOAuth2AuthorizedClientManager non-certificate가 OAuth2AuthorizedClientManager, 을 할 때ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider()하려면) (OAuth2는 OAuth2는 OAuth2는 OAuth2는 OAuth2는 OAuth2는 OAuth2는 OAuth2는 OAuth2는 프로바이더로 해야 .)ReactiveOAuth2AuthorizedClientProvider instead instead instead instead instead instead the instead instead instead OAuth2AuthorizedClientProvidernon-reactive를 사용하는 경우 스프링 보안 참조 문서에 따라DefaultClientCredentialsTokenResponseClient..setRequestEntityConverter()요구를 . 단, 등가 OAuth2 입니다.WebClientReactiveClientCredentialsTokenResponseClient은 이하지 않기 기능을 을 활용할 수 ).WebClientReactiveClientCredentialsTokenResponseClient★★★★★★★★★★★★★★★★★★」

구현이 호출되었습니다.UaaWebClientReactiveClientCredentialsTokenResponseClient 때문에 합니다).headers() ★★★★★★★★★★★★★★★★★」body() 「」로부터의 WebClientReactiveClientCredentialsTokenResponseClient헤더/바디필드를 추가할 경우 기본 인증 흐름은 변경되지 않습니다).

WebClient

ServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient()메서드가 폐지되었으므로 해당 메서드의 권장 해제 조언을 따르십시오.

권장되지 않습니다.대신 사용하세요. (또는 커스텀)으로 설정된 인스턴스를 만듭니다.인스턴스를 에 제공합니다.

그 결과, 다음과 같은 설정이 됩니다.

@Bean("oAuth2WebClient")
public WebClient oauthFilteredWebClient(final ReactiveClientRegistrationRepository 
    clientRegistrationRepository)
{
    final ClientCredentialsReactiveOAuth2AuthorizedClientProvider
        clientCredentialsReactiveOAuth2AuthorizedClientProvider =
            new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
    clientCredentialsReactiveOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(
        new UaaWebClientReactiveClientCredentialsTokenResponseClient());

    final DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager =
        new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository,
            new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
    defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(
        clientCredentialsReactiveOAuth2AuthorizedClientProvider);

    final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuthFilter =
        new ServerOAuth2AuthorizedClientExchangeFilterFunction(defaultReactiveOAuth2AuthorizedClientManager);
    oAuthFilter.setDefaultClientRegistrationId("uaa");

    return WebClient.builder()
        .filter(oAuthFilter)
        .build();
}

WebClient 때처럼

oAuth2WebClient 위해 수 되었습니다.은, 「bean」의 「bean」의 「」의 「Bean」의 「Bean」의 「Bean」의 「Bean」의 「Bean」의 방법에 의한 것입니다.WebClient.

은 간단한 입니다.OAuth2RestTemplate Boot을 3.0.0-M4 없다application.yml설정이 필요합니다.

SecurityConfig.java

    @Bean
    public ReactiveClientRegistrationRepository getRegistration() {
        ClientRegistration registration = ClientRegistration
                .withRegistrationId("custom")
                .tokenUri("<token_URI>")
                .clientId("<client_id>")
                .clientSecret("<secret>")
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .build();
        return new InMemoryReactiveClientRegistrationRepository(registration);
    }

    @Bean
    public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
        InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService);
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultClientRegistrationId("custom");
        return WebClient.builder()
                .filter(oauth)
                .filter(errorHandler()) // This is an optional
                .build();

    }

    public static ExchangeFilterFunction errorHandler() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {

            if (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError()) {
                return clientResponse.bodyToMono(String.class)
                        .flatMap(errorBody -> Mono.error(new IllegalAccessException(errorBody)));
            } else {
                return Mono.just(clientResponse);
            }
        });
    }

pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0-M4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <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-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
    <dependencies>

언급URL : https://stackoverflow.com/questions/58982286/spring-security-5-replacement-for-oauth2resttemplate

반응형