SpringBoot开启SSL双向验证,以及使用OkHttp作为客户端访问
SSL认证 与双向SSL认证
单向的SSL认证,客户端需要校验服务端的身份,并且保证数据的通信是加密的。一般web站点都是采用单向认证。
双向的SSL认证,不仅客户端需要校验服务器的身份,并且服务器也要校验客户端的身份。一般在企业的通信接口之间会采用这种安全系数较高的通信方式。
双向SSL认证,简单点做,需要客户端与服务端交换公钥。并且添加到自己的信任库。这种方式,每次新增用户都需要停机维护,添加新用户的证书。
也可以采用自建CA证书来签发的这种机制来颁发证书。自己维护一个CA证书,所有的客户端包括服务端的证书都由CA签发。有新的客户端,直接用CA这个数给他签发一个证书即可。不再需要服务端手动的导入到新任库。
CA,服务端,客户端的证书生成,以及签发过程
改天我会详写证书的生成,以及签发。还有参数指令。
创建CA证书并且导出公钥
keytool -genkey -deststoretype pkcs12 -alias CA_ROOT -validity 3500 -keystore CA_ROOT.keystore -keyalg RSA -keysize 2048 -storepass 123456
keytool -export -alias CA_ROOT -file CA_ROOT.cer -keystore CA_ROOT.keystore -storepass 123456
创建客户端证书并且创建证书申请
keytool -genkey -deststoretype pkcs12 -alias client -validity 365 -keystore client.keystore -keyalg RSA -keysize 2048 -storepass 123456
keytool -certreq -alias client -file client.csr -keystore client.keystore -storepass 123456
使用CA证书签发客户端证书
keytool -gencert -alias CA_ROOT -infile client.csr -outfile client.cer -keystore CA_ROOT.keystore -storepass 123456
创建服务端证书并且创建证书申请
keytool -genkey -deststoretype pkcs12 -alias server -validity 365 -keystore server.keystore -keyalg RSA -keysize 2048 -storepass 123456
keytool -certreq -alias server -file server.csr -keystore server.keystore -storepass 123456
使用CA证书签发服务端证书
keytool -gencert -alias CA_ROOT -infile server.csr -outfile server.cer -keystore CA_ROOT.keystore -storepass 123456
客户端导入CA签发的证书,以及CA的公钥到keystore
keytool -import -file CA_ROOT.cer -alias ca -keystore client.keystore -storepass 123456
keytool -import -file ca_client.cer -alias client -keystore client.keystore -storepass 123456
服务端导入CA签发的证书,以及CA的公钥到keystore
keytool -import -file CA_ROOT.cer -alias ca -keystore server.keystore -storepass 123456
keytool -import -file ca_server.cer -alias server -keystore server.keystore -storepass 123456
SpringBoot的证书配置
这里把服务器的证书与信任的客户端证书都放在了一个
Keystore
,其实是可以分开存放的
yml配置
server:
port: 443
servlet:
context-path: /
#SSL 配置类:org.springframework.boot.web.server.Ssl
ssl:
enabled: true
# 开启SSL安全通信,并设置证明服务器身份的证书
key-store: classpath:ssl/server.keystore
key-store-type: PKCS12
key-store-password: 123456
# 以下配置为开启SSL双向验证,并且设置信任的客户端证书
client-auth: NEED # 需要验证客户端身份
trust-store: classpath:ssl/server.keystore
trust-store-type: PKCS12
trust-store-password: 123456
提供一个Controller
package io.springboot.ssl.web.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/")
public class IndexController {
@GetMapping
public Object index(){
Map<String,String> map = new HashMap<>();
map.put("name","Javaweb开发者社区");
return map;
}
}
启动服务后尝试使用浏览器访问
无法和服务端建立连接,因为服务端开启了SSL双向验证。而且浏览器没有我们CA签发的证书
使用OkHttp作为客户端访问
客户端代码
package io.springboot.ssl.client;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
public class HttpClient {
// TLS/SSLv3
private static final String PROTOCOL = "TLS"; //SSLv3
// JKS/PKCS12
private static final String KEY_KEYSTORE_TYPE = "JKS";
private static final String SUN_X_509 = "SunX509";
private static SSLContext getSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException, IOException, KeyManagementException, IOException {
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
sslContext.init(keyManagers, trustManagers, new SecureRandom());
return sslContext;
}
private static KeyManager[] getKeyManagers(InputStream keystore, String password)throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException,IOException {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SUN_X_509);
KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE);
keyStore.load(keystore, password.toCharArray());
keyManagerFactory.init(keyStore, password.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
return keyManagers;
}
private static TrustManager[] getTrustManagers(InputStream keystore, String password)throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(SUN_X_509);
KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE);
keyStore.load(keystore, password.toCharArray());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
return trustManagers;
}
public static void main(String[] args)throws Exception {
// 客户端证书的路径
String keystorePath = "C:\\Users\\Administrator\\Desktop\\key\\client\\client.keystore";
// keystore的密码
String keystorePassword = "123456";
KeyManager[] keyManagers = getKeyManagers(Files.newInputStream(Paths.get(keystorePath)),keystorePassword);
TrustManager[] trustManagers = getTrustManagers(Files.newInputStream(Paths.get(keystorePath)),keystorePassword);
SSLContext sslContext = getSslContext(keyManagers,trustManagers);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0])
.hostnameVerifier((host,sslSession) -> {
// 校验证书域名
return true;
})
.build();
Request request = new Request.Builder()
.url("https://localhost:443")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
}