最近几天弄Springboot的https的restful访问,期间遇到一系列的问题记录如下:
springboot项目端口不能定义为 6666等特殊端口
本来想选个酷炫的6666端口启动,结果启动是没有问题,但是通过浏览器访问就没有反应,这个问题后来查询是因为浏览器对一些特殊端口有做限制。具体哪些端口这儿就不记录了,反正6666是不行的。
2.keytool生成证书及tomcat中设置SSL
2.1、Keytool介绍
Keytool是一个Java数据证书的管理工具。Keytool将密钥(key)和证书(certificates)存在一个称为keystore的文件中在keystore里,包含两种数据:
1. 密钥实体(Key entity)——密钥(secret key)又或者是私钥和配对公钥(采用非对称加密)
2. 可信任的证书实体(trusted certificate entries)——只包含公钥
Alias(别名):每个keystore都关联这一个独一无二的alias,这个alias通常不区分大小写
keystore的存储位置
在没有制定生成位置的情况下,keystore会存在与用户的系统默认目录, 如:对于window xp系统,会生成在系统的C:/Documents and Settings/UserName/ 文件名为“.keystore”
keystore的生成:keytool -genkey -alias tomcat -keyalg RSA -keystore d:/mykeystore -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN" -keypass changeit -storepass -validity 36500
参数说明:
-genkey表示要创建一个新的密钥
-dname表示密钥的Distinguished Names,
CN=commonName
OU=organizationUnit
O=organizationName
L=localityName
S=stateName
C=country
Distinguished Names表明了密钥的发行者身份
-keyalg使用加密的算法,这里是RSA
-alias密钥的别名
-keypass私有密钥的密码,这里设置为changeit
-keystore 密钥保存在D:盘目录下的mykeystore文件中
-storepass 存取密码,这里设置为changeit,这个密码提供系统从mykeystore文件中将信息取出
-validity该密钥的有效期为 36500表示100年 (默认为90天)
cacerts证书文件(The cacerts Certificates File)
改证书文件存在于java.home/lib/security目录下,是Java系统的CA证书仓库
2.2、准备工作
1. 验证是否已创建过同名的证书
Window : keytool -list -v -alias tomcat -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
Linux : keytool -list -v -alias tomcat -keystore "$JAVA_HOME/jre/lib/security/cacerts" -storepass changeit
2. 删除已创建的证书
Window : keytool -delete -alias tomcat -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
Linux : keytool -delete -alias tomcat -keystore "$JAVA_HOME/jre/lib/security/cacerts" -storepass changeit
2.3、创建证书
1. 服务器中生成证书:
(注:生成证书时,CN要和服务器的域名相同,如果在本地测试,则使用localhost)
Window : keytool -genkey -alias tomcat -keyalg RSA -keystore d:/my.keystore -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN" -keypass changeit -storepass changeit
Linux : keytool -genkey -alias tomcat -keyalg RSA -keystore ~/my.keystore -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN" -keypass changeit -storepass changeit
2. 导出证书,由客户端安装:
window : keytool -export -alias tomcat -keystore d:/my.keystore -file d:/mycerts.cer -storepass changeit
Linux : keytool -export -alias tomcat -keystore ~/my.keystore -file ~/mycerts.cer -storepass changeit
3. 客户端配置:为客户端的JVM导入密钥(将服务器下发的证书导入到JVM中)
window : keytool -import -trustcacerts -alias tomcat -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -file d:/mycerts.cer -storepass changeit
Linux : keytool -import -trustcacerts -alias tomcat -keystore "$JAVA_HOME/jre/lib/security/cacerts" -file ~/mycerts.cer -storepass changeit
2.4、配置Tomcat SSL
修改server.xml中的SSL服务
Window :
<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" keystoreFile="d:/my.keystore" keystorePass="changeit"/>
Linux:
<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" keystoreFile="~/my.keystore" keystorePass="changeit"/>
2.5、常见问题
1. 未找到可信任的证书
主要原因为在客户端未将服务器下发的证书导入到JVM中,可以用
keytool -list -alias tomcat -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
来查看证书是否真的导入到JVM中。
2. keytool错误:java.io.IOException:keystore was tampered with,or password was incorrect
原因是在你的home目录下是否还有.keystore存在。如果存在那么把他删除掉,后再执行
或者删除"%JAVA_HOME%/jre/lib/security/cacerts 再执行
建议直接删掉cacerts再导入
Tomcat配置https及访问http自动跳转至https
完成上述操作就可以通过https://www.xxx.com:8443 或者 http://www.xxx.com:[port]访问网站;
第二步:配置Tomcat
打开$CATALINA_HOME/conf/server.xml,修改如下:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
修改参数=>
<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="443" />
<!--
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"/>
-->
去掉注释且修改参数=>
<Connector port="443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" keystoreFile="/etc/tomcat.keystore" keystorePass="changeit"/>
<!--
<Connector port="8009" enableLookups="false" protocol="AJP/1.3" redirectPort="8443" />
-->
修改参数=>
<Connector port="8009" enableLookups="false" protocol="AJP/1.3" redirectPort="443" />
第三步:配置tomcat的web.xml在该文件末尾增加:强制https访问
及输入http:// 自动跳转https://
配置如下:
<login-config>
<!-- Authorization setting for SSL -->
<auth-method>CLIENT-CERT</auth-method>
<realm-name>Client Cert Users-only Area</realm-name>
</login-config>
<security-constraint>
<!-- Authorization setting for SSL -->
<web-resource-collection >
<web-resource-name >SSL</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
3.keytool生成证书时指定IP
网上有些资料说,生成证书时只能输入域名,不然用另外一台电脑导入证书后,通过https访问时回报 “java.security.cert.CertificateException: No subject alternative names present”,但是根据我的实际操作后验证,可以在生成证书时设置为IP,操作如下:
服务器端:
keytool -genkey -alias rsso3 -keyalg RSA -keystore e:/my.keystore -dname "CN=10.15.7.173, OU=localhost, O=localhost, L=SH, ST=SH, C=CN" -ext san=ip:10.15.7.173 -keypass changeit -storepass changeit -validity 36500
keytool -export -alias rsso3 -keystore e:/my.keystore -file e:/mycerts.cer -storepass changeit
客户端:
keytool -import -trustcacerts -alias rsso3 -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -file w:/mycerts.cer -storepass changeit
4.Springboot支持https
springboot工程,支持https还是很方便的,一个证书文件,一些简单ssl配置即可:
在application.properties中设置SSL参数,如下:
##(密钥文件路径,也可以配置绝对路径)
server.ssl.key-store=classpath:my.keystore
#(密钥生成时输入的密钥库口令)
server.ssl.key-store-password=changeit
#(密钥类型,与密钥生成命令一致)
#server.ssl.keyStoreType=RSA
#(密钥别名,与密钥生成命令一致)
server.ssl.keyAlias=rsso3
配置好以后,再启动springboot工程,就可以访问https服务了。
5.Springboot中利用RestTemplate访问https的restful服务,绕过证书验证
这步有点特别,能绕过https的证书验证而能访问https接口,这个如果在生产环境会特别不友好!暂时没找到更多的一些信息。只把怎么绕过的代码先放这儿已做记录。
首先得配置RestTemplateConfig的配置文件,代码如下:
import com.barry.springbootdemo.utils.HttpClientUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置模板
*
* @author like
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public RestTemplate httpsRestTemplate(HttpComponentsClientHttpRequestFactory httpsFactory){
RestTemplate restTemplate = new RestTemplate(httpsFactory);
restTemplate.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) {
return false;
}
@Override
public void handleError(ClientHttpResponse clientHttpResponse) {
//默认处理非200的返回,会抛异常
}
});
return restTemplate;
}
@Bean(name = "httpsFactory")
public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() throws Exception{
CloseableHttpClient httpClient = HttpClientUtils.acceptsUntrustedCertsHttpClient();
HttpComponentsClientHttpRequestFactory httpsFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
httpsFactory.setReadTimeout(2000); //单位为ms
httpsFactory.setConnectTimeout(2000); //单位为ms
return httpsFactory;
}
}
然后还得弄一个设置过滤掉安全证书的工具类
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class HttpClientUtils {
public static CloseableHttpClient acceptsUntrustedCertsHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
HttpClientBuilder b = HttpClientBuilder.create();
// setup a Trust Strategy that allows all certificates.
//
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}).build();
b.setSSLContext(sslContext);
// don't check Hostnames, either.
// -- use SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
// here's the special part:
// -- need to create an SSL Socket Factory, to use our weakened "trust strategy";
// -- and create a Registry, to register it.
//
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build();
// now, we create connection-manager using our Registry.
// -- allows multi-threaded use
PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager( socketFactoryRegistry);
connMgr.setMaxTotal(200);
connMgr.setDefaultMaxPerRoute(100);
b.setConnectionManager( connMgr);
// finally, build the HttpClient;
// -- done!
CloseableHttpClient client = b.build();
return client;
}
}
最后创建一个Controller调用方法测试验证:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class HelloController {
@Autowired
private RestTemplate restTemplate;
/**
*
*
* @return
*/
@GetMapping("/world")
public String worldController() {
ResponseEntity response = restTemplate.getForEntity("https://10.15.7.173:8989/rsso/index/hello2",String.class);
HttpHeaders httpHeaders = response.getHeaders(); //响应头
HttpStatus httpStatus = response.getStatusCode(); //响应码
System.out.println("hello==="+response.getBody().toString());
return "yes";
}
}
启动springboot工程,访问这个world的restful接口,验证能绕过证书,调用到https的接口:
页面访问显示: