Spring专题

实现Spring RESTful服务的SSL

  Heartbleed 使得大家对SSL安全问题很重视,TSL是SSL演化的新名词,实际使用时推荐最新技术,本课程介绍如何让基于Spring的REST服务变得SSL/TSL化。

  本课程使用到下面的配置:

 首先,假设一个Spring REST 服务如下:

@Controller

@RequestMapping("/")

public class RestService {

    @RequestMapping(method = RequestMethod.GET)

    @ResponseBody

    public String get() {

        return "Called the get Rest Service";

    }

}

 Web.xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>

<web-app

    version="3.1"

    xmlns="http://xmlns.jcp.org/xml/ns/javaee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

 

    <servlet>

        <servlet-name>rest</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>

            <param-name>contextClass</param-name>

            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>

        </init-param>

        <init-param>

            <param-name>contextConfigLocation</param-name>

            <param-value>com.radcortez.rest.ssl</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

    </servlet>

 

    <servlet-mapping>

        <servlet-name>rest</servlet-name>

        <url-pattern>/</url-pattern>

    </servlet-mapping>

 

    <security-constraint>

        <web-resource-collection>

            <web-resource-name>Rest Application</web-resource-name>

            <url-pattern>/*</url-pattern>

        </web-resource-collection>

        <user-data-constraint>

            <!-- Needed for our application to respond to https requests -->

            <transport-guarantee>CONFIDENTIAL</transport-guarantee>

        </user-data-constraint>

    </security-constraint>

</web-app>

 注意其中security-constraint, user-data-constraint 和 <transport-guarantee>CONFIDENTIAL</transport-guarantee>配置,这些指定这个应用需要一个安全连接。

 运行这个服务,部署应用到TomEE,键入网址:https://localhost:8443/,如果你正常配置了tomcat的SSL配置,浏览https和端口844应该一切正常,返回:Called the Rest Service

 如果现在调用客户端不是一般浏览器,而是一个Java客户端,这时会抛出错误:

Message: I/O error on GET request for "https://localhost:8443/":sun.security.validator.ValidatorException:

Exception: Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

 这是因为客户端JDK并没有你服务器的证书,你需要导入,这里我们展示使用编程方式提供信任蜜月的方式,这样做的好处:

  • 你可以运行应用代码在多个环境(和JDK无关)
  • 你不必每次手工将证书导入JDK
  • 你也不必升级JDK时得记住你的证书
  • 其他原因导致你不能直接向JDK导入证书

 编写代码:

RestClientConfig.java

@Configuration

@PropertySource("classpath:config.properties")

public class RestClientConfig {

    @Bean

    public RestOperations restOperations(ClientHttpRequestFactory clientHttpRequestFactory) throws Exception {

        return new RestTemplate(clientHttpRequestFactory);

    }

 

    @Bean

    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {

        return new HttpComponentsClientHttpRequestFactory(httpClient);

    }

 

    @Bean

    public HttpClient httpClient(@Value("${keystore.file}") String file,

                                 @Value("${keystore.pass}") String password) throws Exception {

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

        FileInputStream instream = new FileInputStream(new File(file));

        try {

            trustStore.load(instream, password.toCharArray());

        } finally {

            instream.close();

        }

 

        SSLContext sslcontext =

                SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();

        SSLConnectionSocketFactory sslsf =

                new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null,

                                               BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);

        return HttpClients.custom().setSSLSocketFactory(sslsf).build();

    }

 

    @Bean

    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {

        return new PropertySourcesPlaceholderConfigurer();

    }

}

这里我们使用Spring RestOperations接口规定一个RESTful操作的基本集合,下面我们使用Apache HTTP组件SSLConnectionSocketFactory 提供的功能来校验服务器的信任密钥,也是使用服务器的 KeyStore

RestServiceClientIT.java

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = RestClientConfig.class)

public class RestServiceClientIT {

    @Autowired

    private RestOperations rest;

 

    @Test

    public void testRestRequest() throws Exception {

        ResponseEntity response = rest.getForEntity("https://localhost:8443/", String.class);

        System.out.println("response = " + response);

        System.out.println("response.getBody() = " + response.getBody());

    }

}

上面是一个简单的测试类,我们需要一个属性文件提供keystore文件位置和密码:

config.properties

keystore.file=${user.home}/.keystore

keystore.pass=changeit

 现在我们可以运行测试客户端,你应该得到如下:

Response: <200 OK,Called the get Rest Service,{Server=[Apache-Coyote/1.1], Cache-Control=[private], Expires=[Thu, 01 Jan 1970 01:00:00 WET], Content-Type=, Content-Length=[27], Date=[Tue, 23 Dec 2014 01:29:20 GMT]}>

Body: Called the get Rest Service

 这说明一切正常,现在,你可以使用Java客户端以SSL/TLS方式调用你的REST服务了。

 本案例源码下载: REST SSL.

 

Spring各种源码项目下载