使用restTemplate发送带认证头的GET请求

103

我需要使用RestTemplate通过发送带有授权标头的GET请求从我的服务器检索资源。

在查看文档后,我注意到没有任何GET方法接受标头作为参数,而发送标头(如接受和授权)的唯一方法是使用exchange方法。

由于这是一个非常基本的操作,我想知道是否有其他更简单的方法可以完成这个操作?

7个回答

76

你没有错过任何东西。 RestTemplate#exchange(..) 是设置请求头的合适方法。

这是一个示例(使用POST,但只需将其更改为GET并使用所需实体即可)。

这是另一个示例。

请注意,在使用GET时,你的请求实体不必包含任何内容(除非你的API需要它,但那会违反HTTP规范)。它可以是一个空字符串。


65

你可以使用 postForObject 和一个 HttpEntity。代码如下:

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer "+accessToken);

HttpEntity<String> entity = new HttpEntity<String>(requestJson,headers);
String result = restTemplate.postForObject(url, entity, String.class);
在一个GET请求中,通常不会发送请求体(尽管允许这样做,但没有任何意义)。如果想要添加标头而不必更改RestTemplate的连线方式,则可以直接使用exchange或execute方法。get快捷方式不支持标头修改。
乍一看,这种不对称有点奇怪,也许在未来的Spring版本中会修复这个问题。

4
restTemplate.postForEntity(url, entity, String.class) 也可以正常工作。 - Abdull
7
问题是关于 GET 请求,但这个答案是关于 POST 的。这很误导人。没有这样签名的 getForObject - Zon

34

这是一个非常简单的示例,包含基本身份验证、头信息和异常处理...

private HttpHeaders createHttpHeaders(String user, String password)
{
    String notEncoded = user + ":" + password;
    String encodedAuth = "Basic " + Base64.getEncoder().encodeToString(notEncoded.getBytes());
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.add("Authorization", encodedAuth);
    return headers;
}

private void doYourThing() 
{
    String theUrl = "http://blah.blah.com:8080/rest/api/blah";
    RestTemplate restTemplate = new RestTemplate();
    try {
        HttpHeaders headers = createHttpHeaders("fred","1234");
        HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
        ResponseEntity<String> response = restTemplate.exchange(theUrl, HttpMethod.GET, entity, String.class);
        System.out.println("Result - status ("+ response.getStatusCode() + ") has body: " + response.hasBody());
    }
    catch (Exception eek) {
        System.out.println("** Exception: "+ eek.getMessage());
    }
}

2
不确定为什么这个最初被投反对票。如果你是第一个投反对票的人,请在评论中解释一下。 - Sander Verhagen
2
“Basic” 部分不应该被编码,是吗? - Wenneguen
1
createHttpHeaders方法略有不妥。 String notEncoded = user + ":" + password; ... headers.add("Authorization", "Basic " + encodedAuth); - Milind
我也刚刚发现“Basic”不应该被编码。这是一个很好的身份验证示例,我自己也使用过,但纠正编码会更好。 - Tim Holt
修复了上面评论中提到的基本编码问题。 - Adriaan Koster

18

现在,类似以下的内容就足够了:

HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
restTemplate.exchange(RequestEntity.get(new URI(url)).headers(headers).build(), returnType);

11
所有这些答案都似乎不完整,或者是笨拙的解决方案。查看 RestTemplate 接口,它确实看起来旨在将 ClientHttpRequestFactory 注入到其中,然后该 requestFactory 将用于创建请求,包括任何自定义的标头、正文和请求参数。
您可以使用通用的 ClientHttpRequestFactory 注入到单个共享的 RestTemplate 中,或者通过 new RestTemplate(myHttpRequestFactory) 获取新的模板实例。
不幸的是,即使只想设置一个 Authorization 标头,创建这样的工厂似乎也有点棘手,这非常令人沮丧,考虑到这可能是一个常见的要求,但至少允许容易使用,例如,如果您的 Authorization 标头可以从 Spring-Security 的 Authorization 对象中包含的数据创建,则可以通过执行 SecurityContextHolder.getContext().getAuthorization() 并相应地填充标头(具有适当的空值检查)来创建设置出站 AuthorizationHeader 的工厂。现在,使用该 RestTemplate 进行的所有出站 REST 调用都将具有正确的 Authorization 标头。
如果没有更多的重视 HttpClientFactory 机制,并为常见情况(如向请求添加单个标头)提供简单的重载基类,那么大部分 RestTemplate 的好用便捷方法最终都会变成浪费时间的东西,因为它们只能很少被使用。
我希望能看到像这样简单的东西可用。
@Configuration
public class MyConfig {
  @Bean
  public RestTemplate getRestTemplate() {
    return new RestTemplate(new AbstractHeaderRewritingHttpClientFactory() {
        @Override
        public HttpHeaders modifyHeaders(HttpHeaders headers) {
          headers.addHeader("Authorization", computeAuthString());
          return headers;
        }
        public String computeAuthString() {
          // do something better than this, but you get the idea
          return SecurityContextHolder.getContext().getAuthorization().getCredential();
        }
    });
  }
}

目前可用的ClientHttpRequestFactory接口比那更难与之交互。更好的方法是为现有工厂实现创建一个抽象包装器,使它们看起来像一个更简单的对象,例如AbstractHeaderRewritingRequestFactory,以替换仅需要更改的这个功能。目前,它们非常通用,因此即使编写这些包装器也是一个复杂的研究工作。


3
您的"答案"非常有趣,但读起来更像是一条评论而不是实际答案。 - Martin Schröder

2

一个简单的解决方案是在RestTemplate的bean配置中配置所有调用所需的静态http头:

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate getRestTemplate(@Value("${did-service.bearer-token}") String bearerToken) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add((request, body, clientHttpRequestExecution) -> {
            HttpHeaders headers = request.getHeaders();
            if (!headers.containsKey("Authorization")) {
                String token = bearerToken.toLowerCase().startsWith("bearer") ? bearerToken : "Bearer " + bearerToken;
                request.getHeaders().add("Authorization", token);
            }
            return clientHttpRequestExecution.execute(request, body);
        });
        return restTemplate;
    }
}

0
注意:从Spring 5开始,RestTemplate类处于维护模式,不建议使用。尽可能使用WebClient
也就是说,由于某些框架的限制或其他原因,您可能仍然需要配置一个RestTemplate。
与每个请求中提供身份验证头部相比,最好配置一个全局RestTemplate,以便不同组件可以重复使用。
以下是一个配置文件,其中包含不同的方法来提供身份验证头部。
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplateWithCustomApiKey(@Value("${api.key}") String apiKey) {
        return new RestTemplateBuilder()
                .requestCustomizers(clientHttpRequest -> clientHttpRequest.getHeaders().add("X-API-Key", apiKey))
                .build();
    }

    @Bean
    public RestTemplate restTemplateWithBearerTokenSupplier(JwtTokenProvider yourJwtTokenProvider) {
        return new RestTemplateBuilder()
                .requestCustomizers(clientHttpRequest -> clientHttpRequest.getHeaders().setBearerAuth(yourJwtTokenProvider.getAccessToken()))
                .build();
    }

    @Bean
    public RestTemplate restTemplateWithBasicAuth(@Value("${auth.user}") String user, @Value("${auth.pass}") String pass) {
        return new RestTemplateBuilder()
                .requestCustomizers(clientHttpRequest -> clientHttpRequest.getHeaders().setBasicAuth(user, pass))
                .build();
    }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接