詳見: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服務了。