概述

在这个教程中,我们将学习如何使用Spring的RestTemplate消费带有基本身份验证的RESTful服务

一旦我们在模板中设置了基本身份验证,每次请求都会预先包含执行身份验证过程所需的完整凭据。这些凭据会被编码,并根据基本身份验证方案的规范使用Authorization HTTP头。一个例子看起来像这样:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

2. 设置RestTemplate

我们可以通过简单地在Spring上下文中声明一个bean来初始化RestTemplate;然而,为了设置带有**基本身份验证的RestTemplate**,我们需要手动干预。因此,我们不会直接声明bean,而是使用Spring的FactoryBean以增加灵活性。这个FactoryBean会在初始化时创建并配置模板:

@Component
public class RestTemplateFactory
  implements FactoryBean<RestTemplate>, InitializingBean {
 
    private RestTemplate restTemplate;

    public RestTemplate getObject() {
        return restTemplate;
    }
    public Class<RestTemplate> getObjectType() {
        return RestTemplate.class;
    }
    public boolean isSingleton() {
        return true;
    }

    public void afterPropertiesSet() {
        HttpHost host = new HttpHost("localhost", 8082, "http");
        restTemplate = new RestTemplate(
          new HttpComponentsClientHttpRequestFactoryBasicAuth(host));
    }
}

hostport值应该依赖于环境,以便客户端可以根据集成测试和生产环境定义不同的值。这些值可以通过Spring对properties文件的一流支持进行管理:使用Properties配置Spring项目

3. 手动管理Authorization HTTP头

对于基本身份验证,我们很容易创建Authorization头,所以我们可以手动编写几行代码来实现:

HttpHeaders createHeaders(String username, String password){
   return new HttpHeaders() {{
         String auth = username + ":" + password;
         byte[] encodedAuth = Base64.encodeBase64( 
            auth.getBytes(Charset.forName("US-ASCII")) );
         String authHeader = "Basic " + new String( encodedAuth );
         set( "Authorization", authHeader );
      }};
}

发送请求同样简单:

restTemplate.exchange
 (uri, HttpMethod.POST, new HttpEntity<T>(createHeaders(username, password)), clazz);

4. 自动管理Authorization HTTP头

Spring 3.0、3.1以及现在的4.x都有对Apache HTTP库的良好支持:

  • 在Spring 3.0中,CommonsClientHttpRequestFactory与现已过时的HttpClient 3.x集成。
  • Spring 3.1引入了对当前HttpClient 4.x的支持,通过HttpComponentsClientHttpRequestFactory(添加支持于JIRA SPR-6180)。
  • Spring 4.0通过HttpComponentsAsyncClientHttpRequestFactory引入了异步支持。

让我们开始使用HttpClient 4和Spring 4进行设置。

RestTemplate需要一个支持基本身份验证的HTTP请求工厂。然而,直接使用现有的HttpComponentsClientHttpRequestFactory可能会很困难,因为RestTemplate的设计没有很好地支持HttpContext,这是解决问题的关键部分。因此,我们需要子类化HttpComponentsClientHttpRequestFactory并重写createHttpContext方法:

public class HttpComponentsClientHttpRequestFactoryBasicAuth 
  extends HttpComponentsClientHttpRequestFactory {

    HttpHost host;

    public HttpComponentsClientHttpRequestFactoryBasicAuth(HttpHost host) {
        super();
        this.host = host;
    }

    protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
        return createHttpContext();
    }
    
    private HttpContext createHttpContext() {
        AuthCache authCache = new BasicAuthCache();

        BasicScheme basicAuth = new BasicScheme();
        authCache.put(host, basicAuth);

        BasicHttpContext localcontext = new BasicHttpContext();
        localcontext.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
        return localcontext;
    }
}

我们在创建HttpContext时构建了基本身份验证支持。如你所见,使用HttpClient 4.x进行预授权的基本身份验证对我们来说有些麻烦。认证信息被缓存,手动设置这个认证缓存既繁琐又不直观。

现在一切就绪,RestTemplate只需添加一个BasicAuthorizationInterceptor即可支持基本身份验证方案:

restTemplate.getInterceptors().add(
  new BasicAuthorizationInterceptor("username", "password"));

然后发送请求:

restTemplate.exchange(
  "http://localhost:8082/spring-security-rest-basic-auth/api/foos/1", 
  HttpMethod.GET, null, Foo.class);

有关如何安全地保护REST服务本身的深入讨论,请参阅这篇文章

5. Maven依赖

RestTemplate本身和HttpClient库需要以下Maven依赖:

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>6.0.13</version>
</dependency>

<dependency>
   <groupId>org.apache.httpcomponents.client5</groupId>
   <artifactId>httpclient5</artifactId>
   <version>5.2.1</version>
</dependency>

如果手动构造HTTP Authorization头,我们还需要一个额外的库来支持编码支持:

<dependency>
   <groupId>commons-codec</groupId>
   <artifactId>commons-codec</artifactId>
   <version>1.10</version>
</dependency>

可以在Maven仓库找到最新版本:spring-webmvchttpclient5commons-codec

6. 结论

关于RestTemplate和安全性方面的许多信息仍然没有考虑到当前的HttpClient 4.x版本,尽管3.x分支已经过时,Spring对这个版本的支持已完全弃用。在这个文章中,我们试图通过详细讲解如何设置带有RestTemplate的基本身份验证并使用它来消费一个受保护的REST API来改变这种情况。

要深入了解本文中的代码示例之外的消费者实现和实际RESTful服务,请查看GitHub上的项目:github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-web-rest-basic-auth

这是一个基于Maven的项目,可以直接导入并运行。