无法使用RestTemplate

时间:2016-11-11 01:09:41

标签: java spring spring-security spring-integration spring-security-oauth2

我是Spring Integration Framework的完整菜鸟。

我正在尝试使用使用OAuth2的REST API。我正在使用基于Spring Integration xml的配置。

我的问题是似乎无法正确连接网关和Rest模板以发送令牌请求的正文(多部分)

这是我的spring集成配置文件:

弹簧整合-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>

要获取初始令牌,我知道我需要在标题中发布我的凭据,类似于:

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

并向身体添加多部分(形式)

grant_type=client_credentials&scope=read

API要求字符串grant_type=client_credentials&scope=read位于请求的正文(而不是网址)中,因为这是POST(不是GET)。

我尝试了一些事情(太多要记住/重新计算所有内容),但是我不确定在何处/如何将有效负载放入我的请求中。

我遗失了一些东西(很明显?)而且我现在还不知道它是什么。

这是我在日志中获得的内容(请求/响应):

  

2016-11-10 16:46:22.429 DEBUG 6384 --- [ask-scheduler-3]   s.n.www.protocol.http.HttpURLConnection:   sun.net.www.MessageHeader@2d1f9cc810对:{POST   [编辑] / oauth / token HTTP / 1.1:null} {授权:基本   [删除]} {接受:   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} {主持人:   [编辑]} {连接:保持活着} {内容长度:29}   2016-11-10 16:46:22.584 DEBUG 6384 --- [ask-scheduler-3]   s.n.www.protocol.http.HttpURLConnection:   sun.net.www.MessageHeader@29200db310对:{null:HTTP / 1.1 400 Bad   请求} {Cache-Control:no-cache} {Pragma:no-cache} {Content-Type:   应用/ JSON; charset = utf-8} {Expires:-1} {Server:   [编辑]} {[编辑]} {[编辑]} {日期:星期五,2016年11月11日00:46:48 GMT} {内容长度:46}   2016-11-10 16:46:22.586 ERROR 6384 --- [ask-scheduler-3]   o.s.integration.handler.LoggingHandler:   org.springframework.messaging.MessageHandlingException: HTTP请求   URI的执行失败   [删除];   嵌套异常是错误=&#34; access_denied&#34;,error_description =&#34;访问   令牌被拒绝。&#34; at   org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.handleRequestMessage(HttpRequestExecutingMessageHandler.java:409)     在   org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109)     在   org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:127)     在   org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)     在   org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:148)     在   org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121)     在   org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)     在   org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)     在   org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)     在   org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)     在   org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)     在   org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105)     在   org.springframework.integration.endpoint.SourcePollingChannelAdapter.handleMessage(SourcePollingChannelAdapter.java:195)     在   org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:272)     在   org.springframework.integration.endpoint.AbstractPollingEndpoint.access $ 000(AbstractPollingEndpoint.java:58)     在   org.springframework.integration.endpoint.AbstractPollingEndpoint $ 1.call(AbstractPollingEndpoint.java:190)     在   org.springframework.integration.endpoint.AbstractPollingEndpoint $ 1.call(AbstractPollingEndpoint.java:186)     在   org.springframework.integration.endpoint.AbstractPollingEndpoint $轮询$ 1.run(AbstractPollingEndpoint.java:353)     在   org.springframework.integration.util.ErrorHandlingTaskExecutor $ 1.run(ErrorHandlingTaskExecutor.java:55)     在   org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)     在   org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:51)     在   org.springframework.integration.endpoint.AbstractPollingEndpoint $ Poller.run(AbstractPollingEndpoint.java:344)     在   org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)     在   org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)     在   java.util.concurrent.Executors $ RunnableAdapter.call(Executors.java:511)     在java.util.concurrent.FutureTask.run(FutureTask.java:266)at   java.util.concurrent.ScheduledThreadPoolExecutor中的$ ScheduledFutureTask.access $ 201(ScheduledThreadPoolExecutor.java:180)     在   java.util.concurrent.ScheduledThreadPoolExecutor中的$ ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)     在   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)     在   java.util.concurrent.ThreadPoolExecutor中的$ Worker.run(ThreadPoolExecutor.java:617)     在java.lang.Thread.run(Thread.java:745)引起:   错误=&#34; access_denied&#34;,error_description =&#34;访问令牌被拒绝。&#34; at   org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:142)     在   org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider.obtainAccessToken(ClientCredentialsAccessTokenProvider.java:44)     在   org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:142)     在   org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118)     在   org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)     在   org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)     在   org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)at at   org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:615)     在   org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128)     在   org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)     在   org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:516)     在   org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.handleRequestMessage(HttpRequestExecutingMessageHandler.java:382)     ... 30更多引起:错误=&#34; invalid_request&#34;,   error_description =&#34; OAuth错误&#34;,message =&#34; {&#34;错误&#34;:&#34; invalid_scope&#34;   }&#34; at   org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:120)     在   org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:33)     在   com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789)     在   com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913)     在   org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225)     在   org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readInternal(AbstractJackson2HttpMessageConverter.java:205)     在   org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:193)     在   org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport $ AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:235)     在   org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)     在   org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)     在   org.springframework.web.client.RestTemplate.execute(RestTemplate.java:588)     在   org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:137)     ......还有41个

所以,从我所知道的,我得到了一个“拒绝访问”#34;消息,因为我无法在请求正文中设置范围。

注1

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

注2

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

更新1

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

<!-- 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;
    }
}

然而,结果是相同的:/

2 个答案:

答案 0 :(得分:1)

因此,您真的要提供POST而不是当前的http-method="GET",并将消息发送到该网关,payload作为所需对的Map

答案 1 :(得分:1)

最后,我最终没有使用Oauth2RestTemplate

相反,我使用下面的代码。代码基本上是通过创建Map有效负载(如Artem在他的回答中所建议的)开始的,并为第一个请求注入头。

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

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

<强> 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;
    }
}

<强>弹簧整合-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>