详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt386
本文介绍如何让基于Spring的REST服务变得SSL/TSL化。
首先,假设一个Spring REST 服务如下:
1
2
3
4
5
6
7
8
9
|
@Controller @RequestMapping ( "/" ) public class RestService { @RequestMapping (method = RequestMethod.GET) @ResponseBody public String get() { return "Called the get Rest Service" ; } } |
Web.xml的配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
<? 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导入证书
编写代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@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服务了。