SpringBoot开启SSL双向验证,以及使用OkHttp作为客户端访问

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());
    }
}

执行结果,成功访问到服务端提供的http服务

项目代码

keytool 工具的详细命令

1 个赞