无法使用 RestTemplate 连接 Spring-Integration

Unable to wire Spring-Integration with RestTemplate

我完全是 Spring 集成框架的菜鸟。

我正在尝试使用使用 OAuth2 的 REST API。我正在使用 Spring 集成 xml-based 配置。

我的问题是似乎无法正确连接网关和 Rest 模板以发送令牌请求的 body (multi-part)

这在我的 spring 集成配置文件中:

spring-integration-context.xml

<!-- Rest Template -->
<bean id="oAuth2RestTemplate"
      class="org.springframework.security.oauth2.client.OAuth2RestTemplate">
    <constructor-arg ref="clientCredentialsResource"/>
</bean>

<!-- Used by Rest Template -->
<bean id= "clientCredentialsResource"
      class="org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails">
    <property name="clientId" value="${oauth2.clientId}" />
    <property name="clientSecret" value="${oauth2.clientSecret}" />
    <property name="accessTokenUri" value="${oauth2.accessTokenUri}" />
</bean>

<!-- Channels for requesting token -->
<int:channel id="tokenRequestChannel"/>
<int:channel id="tokenResponseChannel"/>

<!-- Gateway for requesting token -->
<int-http:outbound-gateway id="authRequestGateway"
                           request-channel="tokenRequestChannel"
                           url="${oauth2.endPointUri}"
                           reply-timeout="30000"
                           http-method="GET"
                           rest-template="oAuth2RestTemplate"
                           reply-channel="tokenResponseChannel"
                           charset="UTF-8"
                           expected-response-type="java.lang.String">
</int-http:outbound-gateway>

要获得初始令牌,我知道我需要 POST 我在 header 中的凭据类似于:

Method: POST
Authorization: Basic <base64-encoded clientId:clientSecret>
Content-Type: application/x-www-form-urlencoded

并在 body

中添加 multi-part(表格)
grant_type=client_credentials&scope=read

API 要求字符串 grant_type=client_credentials&scope=read 在请求的 body(不是 URL)中,因为这是一个 POST(不是GET).

我已经尝试了一些东西(太多了 remember/recount 全部)但是 我不确定 where/how 将负载放入我的请求中。

我遗漏了一些东西(很明显?),我现在不知道是什么。

这是我在日志中得到的 (request/response):

2016-11-10 16:46:22.429 DEBUG 6384 --- [ask-scheduler-3] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@2d1f9cc810 pairs: {POST [redacted]/oauth/token HTTP/1.1: null}{Authorization: Basic [redacted]}{Accept: application/json, application/x-www-form-urlencoded}{Content-Type: application/x-www-form-urlencoded}{Cache-Control: no-cache}{Pragma: no-cache}{User-Agent: Java/1.8.0_72}{Host: [redacted]}{Connection: keep-alive}{Content-Length: 29} 2016-11-10 16:46:22.584 DEBUG 6384 --- [ask-scheduler-3] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@29200db310 pairs: {null: HTTP/1.1 400 Bad Request}{Cache-Control: no-cache}{Pragma: no-cache}{Content-Type: application/json; charset=utf-8}{Expires: -1}{Server: [redacted]}{[redacted]}{[redacted]}{Date: Fri, 11 Nov 2016 00:46:48 GMT}{Content-Length: 46} 2016-11-10 16:46:22.586 ERROR 6384 --- [ask-scheduler-3] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: HTTP request execution failed for URI [redacted]; nested exception is error="access_denied", error_description="Access token denied." at org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.handleRequestMessage(HttpRequestExecutingMessageHandler.java:409) at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109) at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:127) at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116) at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:148) at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121) at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77) at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423) at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373) at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115) at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45) at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105) at org.springframework.integration.endpoint.SourcePollingChannelAdapter.handleMessage(SourcePollingChannelAdapter.java:195) at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:272) at org.springframework.integration.endpoint.AbstractPollingEndpoint.access[=20=]0(AbstractPollingEndpoint.java:58) at org.springframework.integration.endpoint.AbstractPollingEndpoint.call(AbstractPollingEndpoint.java:190) at org.springframework.integration.endpoint.AbstractPollingEndpoint.call(AbstractPollingEndpoint.java:186) at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:353) at org.springframework.integration.util.ErrorHandlingTaskExecutor.run(ErrorHandlingTaskExecutor.java:55) at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:51) at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:344) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: error="access_denied", error_description="Access token denied." at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:142) at org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider.obtainAccessToken(ClientCredentialsAccessTokenProvider.java:44) at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:142) at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118) at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:615) at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:516) at org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.handleRequestMessage(HttpRequestExecutingMessageHandler.java:382) ... 30 more Caused by: error="invalid_request", error_description="OAuth Error", message="{ "error": "invalid_scope" }" at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:120) at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:33) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readInternal(AbstractJackson2HttpMessageConverter.java:205) at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:193) at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:235) at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:588) at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:137) ... 41 more

因此,据我所知,我收到一条 "Access Denied" 消息,因为我无法在请求的 body 中设置范围。

注1

此外,我已经查看了示例项目中的代码,但没有看到我的问题的答案。

注2

我已验证(通过 POSTMAN)凭据、端点 (URI)、范围等都是正确的。

更新 1

根据 @Artem-Bilan 的评论,我将方法更改为 POST 并添加了下面的代码以在请求中包含 body。

<!-- Add Payload -->
<int:inbound-channel-adapter id="oauth2ChannelAdapter" channel="tokenRequestChannel"
                             ref="grantAndScope" method="getGrantTypeAndScope">
    <!-- Triggering requests every 5 seconds -->
    <int:poller fixed-delay="5000" />
</int:inbound-channel-adapter>

<!-- Bean with Grant & Scope Payload -->
<bean id="grantAndScope" class="com.AuthTypeAndScopeInfo"/>

AuthTypeAndScopeInfo.java

public class AuthTypeAndScopeInfo {

    /* adds grant type and scope as body to message */
    public MultiValueMap<String, String> getGrantTypeAndScope() {
        // Create the request body as a MultiValueMap
        MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();

        body.add("grant_type", "client_credentials");
        body.add("scope", "read");

        return body;
    }
}

然而,结果是一样的:/

所以,您真的必须提供 POST 而不是当前的 http-method="GET" 并将消息发送到带有 payload 的网关作为所需的 Map对。

最后,我没有使用Oauth2RestTemplate

相反,我使用了下面的代码。代码基本上从创建一个 Map 有效载荷开始(正如 Artem 在他的回答中所建议的那样)并为第一个请求注入 headers。

然后它使用第一个请求的响应(包含访问令牌)将获得的令牌注入所有后续请求的header。

这里可能还有一些优化的空间,但就目前而言,这足以满足我的需求。

AuthTypeAndScopeInfo.java

public class AuthTypeAndScopeInfo {

    /* adds grant type and scope as body to message */
    public MultiValueMap<String, String> getGrantTypeAndScope() {
        // Create the request body as a MultiValueMap
        MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();

        body.add("grant_type", "client_credentials");
        body.add("scope", "read");

        return body;
    }
}

spring-integration-context.xml

<!-- Add Payload -->
<int:inbound-channel-adapter id="oauth2ChannelAdapter" channel="preTokenRequestChannel"
                             ref="grantAndScope" method="getGrantTypeAndScope">
    <!-- Triggering requests every 5 seconds -->
    <int:poller fixed-delay="5000" />
</int:inbound-channel-adapter>

<!-- POJO with Grant & Scope Payload -->
<bean id="grantAndScope" class="com.AuthTypeAndScopeInfo"/>

<!-- Adding headers for Token Request -->
<int:header-enricher input-channel="preTokenRequestChannel" 
                     output-channel="tokenRequestChannel">
    <int:header name="Authorization" value="Basic <clientId:clientSecret>"/>
    <int:header name="Content-Type" value="application/x-www-form-urlencoded"/>
</int:header-enricher>

<!-- Channels for requesting token -->
<int:channel id="tokenRequestChannel"/>
<int:channel id="tokenResponseChannel"/>

<!-- Channels for Authenticated requests (with valid token) -->
<int:channel id="authenticatedRequestChannel"/>
<int:channel id="authenticatedResponseChannel"/>

<!-- Gateway for requesting token -->
<!-- REST request to authorization server for a token -->
<!-- replies time out after 30 seconds -->
<int-http:outbound-gateway id="tokenRequestGateway"
                           request-channel="tokenRequestChannel"
                           url="${security.oauth2.client.accessTokenUri}"
                           reply-timeout="30000"
                           http-method="POST"
                           reply-channel="tokenResponseChannel"
                           charset="UTF-8"
                           expected-response-type="java.lang.String">
</int-http:outbound-gateway>

<!-- Adding headers for Authenticated Request (contains newly obtained token) -->
<int:chain input-channel="tokenResponseChannel"
           output-channel="authenticatedRequestChannel">
    <int:header-enricher>
        <!-- Adds token_type and the actual (authenticated) 
             access_token to the header of the next request 
             (overwriting the Basic <...> previous entry) -->
        <int:header name="Authorization" overwrite="true"
                    expression="#jsonPath(payload,'$.token_type') + ' ' + #jsonPath(payload,'$.access_token')" />
    </int:header-enricher>
</int:chain>

<!-- REST request with pre-authorized token -->
<!-- replies time out after 30 seconds -->
<int-http:outbound-gateway id="authenticatedRequestGateway"
                           request-channel="authenticatedRequestChannel"
                           url="${security.oauth2.client.endPointUri}"
                           reply-timeout="30000"
                           http-method="GET"
                           reply-channel="authenticatedResponseChannel"
                           charset="UTF-8"
                           expected-response-type="java.lang.String">
</int-http:outbound-gateway>