Spring WebClient POST на login.microsoftonline.com дает ошибку 404 - PullRequest
0 голосов
/ 03 августа 2020

У меня проблема с реализацией Oauth2 с Azure AD с использованием Spring Reactive WebClient (org.springframework.web.reactive.function.client.WebClient) в Kotlin, следуя этой документации: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#first -case-access-token-request-with -a-shared-secret

У моей организации есть собственный клиент, но проблема также возникает при использовании https://login.microsoftonline.com/common/oauth2/v2.0/token.

Если я сделаю следующее CURL, я получаю ожидаемую ошибку 400, в которой мне нужен параметр grant_type:

curl -d '' https://login.microsoftonline.com/common/oauth2/v2.0/token

Что нормально, это означает, что я получаю правильную ошибку от службы, и я могу работа над включением недостающих параметров. Однако, используя WebClient для того же uri, я получаю ошибку 404.

Это моя реализация:

import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient


@Component
class AzureADClient {

    fun oidcToken(): String {
        var received = WebClient.create("https://login.microsoftonline.com/common/oauth2/v2.0/token").post()
            .retrieve()
            .bodyToMono(OidcToken::class.java)
            .block()

        return received.token
    }

    data class OidcToken(
        @JsonProperty(value = "access_token", required = true)
        val token: String,
        @JsonProperty(value = "token_type", required = true)
        val type: String,
        @JsonProperty(value = "expires_in", required = true)
        val expiresIn: Int
    )
}

И это трассировка стека, которую я получаю:

org.springframework.web.reactive.function.client.WebClientResponseException$NotFound: 404 Not Found from POST https://login.microsoftonline.com/common/oauth2/v2.0/token
    at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:185)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ 404 from POST https://login.microsoftonline.com/common/oauth2/v2.0/token [DefaultWebClient]
Stack trace:
        at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:185)
        at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:209)
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:90)
        at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1712)
        at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onComplete(FluxDefaultIfEmpty.java:100)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:144)
        at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onComplete(FluxContextStart.java:122)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:144)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onComplete(FluxFilterFuseable.java:165)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1713)
        at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:160)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252)
        at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:104)
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136)
        at reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:421)
        at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:211)
        at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:369)
        at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:367)
        at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:416)
        at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:612)
        at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:90)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:321)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:295)
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1486)
        at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1235)
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1282)
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:498)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:437)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:834)

Я использую WebClient с POST запросами в других взаимодействиях. Если я заменю вызов WebClient на RestTemplate().postForObject(), я получу ожидаемую ошибку 400, но я пытаюсь полностью использовать WebClient в своем приложении. Я попытался сделать аналогичные POST и https://postman-echo.com/post, и получил разумный ответ, так что он, кажется, указывает c на конечную точку login.microsoftonline.com.

Кто-нибудь сталкивался с этим, и нашли решение?

1 Ответ

1 голос
/ 04 августа 2020

Я нашел решение. Прежде всего, ответ на этот пост предполагает, что хост не принимает кодировку фрагментированной передачи: https://social.msdn.microsoft.com/Forums/en-US/8efc2e39-8e9c-4449-8e25-13b97a7acf15/azure-ad-oauth-20-token-endpoint-recently-started-returning-404-with-empty-response-body-but-used?forum=WindowsAzureAD

Это подтверждается curl:

> curl -H "Transfer-Encoding: chunked" -d ''  https://login.microsoftonline.com/common/oauth2/v2.0/token -v
< ...
< HTTP/1.1 404 Not Found
< ...

Сначала я попытался расширить свой запрос, включив в него требуемые параметры, например:

    fun oidcToken(): String {

        val map = LinkedMultiValueMap<String, String>()

        map.add("client_id", "client_id")
        map.add("client_secret", "client_secret")
        map.add("grant_type", "grant_type")
        map.add("scope", "api://scope_id/.default")

        val received = WebClient
            .create("https://login.microsoftonline.com/common/oauth2/v2.0/token")
            .post()
            .body(BodyInserters.fromMultipartData(map))
            .retrieve()
            .bodyToMono(OidcToken::class.java)
            .block()

        return received.token
    }

Это дало ту же ошибку 404, что и раньше, поэтому передача, вероятно, также разделена на части.

Решение оказалось использовать .bodyValue(map) вместо .body(BodyInserters.fromMultipartData(map)).

Я попытался установить заголовок длины содержимого, следуя этому ответу: { ссылка }. При использовании этого метода я получил ошибку 400, поэтому сервер был найден, но bodyValue() оказалось более простым решением.

...