OAuth2RestTemplate 将字符集添加到内容类型标头中

Posted

技术标签:

【中文标题】OAuth2RestTemplate 将字符集添加到内容类型标头中【英文标题】:OAuth2RestTemplate adds charset into content type header 【发布时间】:2019-08-27 21:56:20 【问题描述】:

当尝试使用 OAuth2RequestTemplate 获取访问令牌时,调用会在请求中发送以下标头

"Content-Type", "application/x-www-form-urlencoded;charset=UTF-8”

我想删除不包含在其中的字符集。

我尝试通过 AccessTokenRequest 对象和 CustomTokenProvider 手动设置标头值,但没有成功。

知道为什么它实际上包含它以及如何摆脱它。

更新:包括代码示例

选项 1:

String oauthServerUri = "..../access_token";

ClientCredentialsResourceDetails ccDetails = new ClientCredentialsResourceDetails();
ccDetails.setClientId("clientId");
ccDetails.setClientSecret("clientSecret");
ccDetails.setGrantType("client_credentials");
ccDetails.setAccessTokenUri(oauthServerUri);

AccessTokenRequest tokenRequest = new DefaultAccessTokenRequest();

Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", Arrays.asList("Some Proper Value"));
tokenRequest.setHeaders(headers);
OAuth2ClientContext context = new DefaultOAuth2ClientContext(tokenRequest);

OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(ccDetails, context);
OAuth2AccessToken accessToken = restTemplate.getAccessToken();

选项 2: 作为替代方案,我尝试了此处描述的方法: How to set HTTP Header for OAuth2RestTemplate 这是实现AccessTokenProvider 并在obtainAccessToken 中设置标题。但这也无济于事。

【问题讨论】:

字符集有什么问题?应该没有理由删除它。所有服务器都应该处理它。 @dur 我现在正在使用一个 API,当 charset 包含在标头中时它会拒绝请求。我知道这通常不是问题,但在我的特殊情况下,我需要找到解决方案。 我无法更改服务器,因为它不是我们的代码,也无法访问它。所以我需要找到一个解决方法。 【参考方案1】:

当你让构造函数 new OAuth2RestTemplate(ccDetails, context);它后面是一个 super(),它是一个 RestTemplate,因为它是从它扩展而来的。

public OAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) 
        super();
        if (resource == null) 
            throw new IllegalArgumentException("An OAuth2 resource must be supplied.");
        

        this.resource = resource;
        this.context = context;
        setErrorHandler(new OAuth2ErrorHandler(resource));
    

RestTemplate 构造函数默认放置 messageConverters。

public RestTemplate() 
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
......

也许一个解决方案可能是,一旦创建了对象 OAuth2RestTemplate,您使用您感兴趣的 MediaType 创建一个 restTemplate.setMessageConverters(messageConverters),从此方法(在 RestTemplate 类中)删除以前的方法:

public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) 
        Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
        // Take getMessageConverters() List as-is when passed in here
        if (this.messageConverters != messageConverters) 
            this.messageConverters.clear();
            this.messageConverters.addAll(messageConverters);
        
    

编辑:

如果你看到上面的图片,你可以看到:

restTemplate.getAccessToken();

打电话给

getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap());

protected RequestCallback getRequestCallback(OAuth2ProtectedResourceDetails resource,
        MultiValueMap<String, String> form, HttpHeaders headers) 
    return new OAuth2AuthTokenCallback(form, headers);

看看他的构造函数做了什么:

/**
     * Request callback implementation that writes the given object to the request stream.
     */
    private class OAuth2AuthTokenCallback implements RequestCallback 

        private final MultiValueMap<String, String> form;

        private final HttpHeaders headers;

        private OAuth2AuthTokenCallback(MultiValueMap<String, String> form, HttpHeaders headers) 
            this.form = form;
            this.headers = headers;
        

        public void doWithRequest(ClientHttpRequest request) throws IOException 
            request.getHeaders().putAll(this.headers);
            request.getHeaders().setAccept(
                    Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED));
            FORM_MESSAGE_CONVERTER.write(this.form, MediaType.APPLICATION_FORM_URLENCODED, request);
        
    

也许通过继承和覆盖方法,您可以将您感兴趣的标头传递给该构建器。

在 OAuth2AccessTokenSupport 中,retrieveToken 方法也有有趣的 cmets:

// Prepare headers and form before going into rest template call in case the URI is affected by the result
authenticationHandler.authenticateTokenRequest(resource, form, headers);
// Opportunity to customize form and headers
tokenRequestEnhancer.enhance(request, resource, form, headers);

希望对你有所帮助。

【讨论】:

对不起,我在这里看不到与 MediaTypes 的连接。我实际上已经研究过删除所有消息转换器的选项,但这并没有帮助......你尝试过你建议的方法吗?标题中有什么? 感谢您为帮助 Francesc 所做的努力。然而,这不是关于添加标题,而是确保字符集不在标题中。如果您想对此进行调查,请尝试使用我的示例,看看会发生什么。正如 dur 指出的那样,问题在于 FormHttpMessageConverter 设置了字符集。 这里是代码的参考,如果你想看的话。 github.com/spring-projects/spring-framework/blob/…【参考方案2】:

您可以使用拦截器来包装您的请求,请参阅ClientHttpRequestInterceptor#intercept

拦截

ClientHttpResponse 拦截(HttpRequest 请求,
                             字节 [] 正文,
                             ClientHttpRequestExecution 执行)
                      抛出 IOException 

拦截给定的请求,并返回响应。给定的ClientHttpRequestExecution 允许拦截器将请求和响应传递给链中的下一个实体。

此方法的典型实现将遵循以下模式:

    检查请求和正文 可以选择包装请求以过滤 HTTP 属性。 可选择修改请求的正文。 要么 使用ClientHttpRequestExecution.execute(org.springframework.http.HttpRequest, byte[])执行请求 不执行请求以完全阻止执行。 可以选择包装响应以过滤 HTTP 属性。

您修改后的代码:

OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(ccDetails, context);
restTemplate.setInterceptors(Arrays.asList(new new RestTemplateHeaderModifierInterceptor()));
OAuth2AccessToken accessToken = restTemplate.getAccessToken();

public class RestTemplateHeaderModifierInterceptor implements ClientHttpRequestInterceptor 

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException 
        HttpRequest requestWrapper = new CustomHttpRequestWrapper(request);
        return execution.execute(requestWrapper, body);
    


public class CustomHttpRequestWrapper extends HttpRequestWrapper 

    public CustomHttpRequestWrapper(HttpRequest request) 
        super(request)
            

    @Override
    public HttpHeaders getHeaders() 
        // return all headers, but change the charset
    

【讨论】:

以上是关于OAuth2RestTemplate 将字符集添加到内容类型标头中的主要内容,如果未能解决你的问题,请参考以下文章

无法将 @LoadBalanced 与在 ClientCredentials 上配置的 OAuth2RestTemplate 一起使用

带有预取 JWT 令牌的 OAuth2RestTemplate

如何设置 OAuth2RestTemplate(更新后)

如何使用 OAuth2RestTemplate + Spring 4?

在安全上下文中保存 Oauth2RestTemplate/Client 存储

OAthe2 Login use OkHttpClient and OAuth2RestTemplate