問題1:什么叫做客戶端證書認證?
答案:通過客戶端證書(服務端分發的私人證書或者是通過第三方認證的證書)+賬號密碼進行身份認證的行為。
問題2:為什么有客戶端證書這么個東西?
答案:如何加強服務器的用戶身份驗證系統?一個方法是通過服務端證書認證,就是通過https進行訪問。另一個方法是客戶端證書認證。通過暴力破解的方式任然可以獲取到用戶密碼,盡管有強密碼策略,僅僅只是依靠密碼還是不太保險,這個時候就需要客戶端證書認證。用戶通過服務端頒發的證書+密碼,可以極大的加強安全性。
問題3:怎么使用客戶端證書認證?
答案:如下文
客戶端證書認證
1.首先准備一個證書(本例以私人證書為例,證書生成方式具體請度娘),記錄下證書最后的加密密碼。
2.服務器配置相應的客戶端證書認證設置(本例不涉及服務端配置)
3.配置java類庫信任個人證書
1 package sz.shuwen.utils; 2 /* 3 * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * - Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * - Neither the name of Sun Microsystems nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 /** 33 * Originally from: 34 * http://blogs.sun.com/andreas/resource/InstallCert.java 35 * Use: 36 * java InstallCert hostname 37 * Example: 38 *% java InstallCert ecc.fedora.redhat.com 39 */ 40 41 import javax.net.ssl.*; 42 import java.io.*; 43 import java.security.KeyStore; 44 import java.security.MessageDigest; 45 import java.security.cert.CertificateException; 46 import java.security.cert.X509Certificate; 47 48 /** 49 * Class used to add the server's certificate to the KeyStore 50 * with your trusted certificates. 51 */ 52 public class InstallCert { 53 54 /*public static void main(String[] args) throws Exception { 55 handle("XXX.com",443,"changeit"); 56 }*/ 57 58 public static void handle(String host,int port,String passphraseStr) throws Exception{ 59 char[] passphrase=passphraseStr.toCharArray(); 60 /*if ((args.length == 1) || (args.length == 2)) { 61 String[] c = args[0].split(":"); 62 host = c[0]; 63 port = (c.length == 1) ? 443 : Integer.parseInt(c[1]); 64 String p = (args.length == 1) ? "changeit" : args[1]; 65 passphrase = p.toCharArray(); 66 } else { 67 System.out.println("Usage: java InstallCert <host>[:port] [passphrase]"); 68 return; 69 }*/ 70 71 File file = new File("cacerts"); 72 if (file.isFile() == false) { 73 char SEP = File.separatorChar; 74 File dir = new File(System.getProperty("java.home") + SEP 75 + "lib" + SEP + "security"); 76 file = new File(dir, "jssecacerts"); 77 if (file.isFile() == false) { 78 file = new File(dir, "cacerts"); 79 } 80 } 81 System.out.println("Loading KeyStore " + file + "..."); 82 InputStream in = new FileInputStream(file); 83 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 84 ks.load(in, passphrase); 85 in.close(); 86 87 SSLContext context = SSLContext.getInstance("TLS"); 88 TrustManagerFactory tmf = 89 TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 90 tmf.init(ks); 91 X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0]; 92 SavingTrustManager tm = new SavingTrustManager(defaultTrustManager); 93 context.init(null, new TrustManager[]{tm}, null); 94 SSLSocketFactory factory = context.getSocketFactory(); 95 96 System.out.println("Opening connection to " + host + ":" + port + "..."); 97 SSLSocket socket = (SSLSocket) factory.createSocket(host, port); 98 socket.setSoTimeout(10000); 99 try { 100 System.out.println("Starting SSL handshake..."); 101 socket.startHandshake(); 102 socket.close(); 103 System.out.println(); 104 System.out.println("No errors, certificate is already trusted"); 105 } catch (SSLException e) { 106 System.out.println(); 107 e.printStackTrace(System.out); 108 } 109 110 X509Certificate[] chain = tm.chain; 111 if (chain == null) { 112 System.out.println("Could not obtain server certificate chain"); 113 return; 114 } 115 116 BufferedReader reader = 117 new BufferedReader(new InputStreamReader(System.in)); 118 119 System.out.println(); 120 System.out.println("Server sent " + chain.length + " certificate(s):"); 121 System.out.println(); 122 MessageDigest sha1 = MessageDigest.getInstance("SHA1"); 123 MessageDigest md5 = MessageDigest.getInstance("MD5"); 124 for (int i = 0; i < chain.length; i++) { 125 X509Certificate cert = chain[i]; 126 System.out.println 127 (" " + (i + 1) + " Subject " + cert.getSubjectDN()); 128 System.out.println(" Issuer " + cert.getIssuerDN()); 129 sha1.update(cert.getEncoded()); 130 System.out.println(" sha1 " + toHexString(sha1.digest())); 131 md5.update(cert.getEncoded()); 132 System.out.println(" md5 " + toHexString(md5.digest())); 133 System.out.println(); 134 } 135 //注釋 136 /*System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]"); 137 String line = reader.readLine().trim();*/ 138 String line="1"; 139 int k; 140 try { 141 k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1; 142 } catch (NumberFormatException e) { 143 System.out.println("KeyStore not changed"); 144 return; 145 } 146 147 X509Certificate cert = chain[k]; 148 String alias = host + "-" + (k + 1); 149 ks.setCertificateEntry(alias, cert); 150 151 OutputStream out = new FileOutputStream("jssecacerts"); 152 ks.store(out, passphrase); 153 out.close(); 154 155 System.out.println(); 156 System.out.println(cert); 157 System.out.println(); 158 System.out.println 159 ("Added certificate to keystore 'jssecacerts' using alias '" 160 + alias + "'"); 161 } 162 163 164 private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); 165 166 private static String toHexString(byte[] bytes) { 167 StringBuilder sb = new StringBuilder(bytes.length * 3); 168 for (int b : bytes) { 169 b &= 0xff; 170 sb.append(HEXDIGITS[b >> 4]); 171 sb.append(HEXDIGITS[b & 15]); 172 sb.append(' '); 173 } 174 return sb.toString(); 175 } 176 177 private static class SavingTrustManager implements X509TrustManager { 178 179 private final X509TrustManager tm; 180 private X509Certificate[] chain; 181 182 SavingTrustManager(X509TrustManager tm) { 183 this.tm = tm; 184 } 185 186 public X509Certificate[] getAcceptedIssuers() { 187 188 /** 189 * This change has been done due to the following resolution advised for Java 1.7+ 190 http://infposs.blogspot.kr/2013/06/installcert-and-java-7.html 191 **/ 192 193 return new X509Certificate[0]; 194 //throw new UnsupportedOperationException(); 195 } 196 197 public void checkClientTrusted(X509Certificate[] chain, String authType) 198 throws CertificateException { 199 throw new UnsupportedOperationException(); 200 } 201 202 public void checkServerTrusted(X509Certificate[] chain, String authType) 203 throws CertificateException { 204 this.chain = chain; 205 tm.checkServerTrusted(chain, authType); 206 } 207 } 208 }
該類是在github上找的(感謝大佬),我注釋的兩處便是修改的地方,可也以取上面的鏈接看看大佬的代碼,我把原方法通過main啟動改為通過接口調用傳參了。
4.調用
本例介紹兩種調用方式:
a.通過postman調用
關閉公有證書校驗,因為本例是個人證書,若是公有證書可以開啟。
添加域名證書配置,作用是訪問該域名才會攜帶配置的相應證書
CRT FILE:證書文件(含公鑰)
KEY FILE:私鑰文件
PFX FILE: 后綴為p12的加密證書文件
Passphrase:加密證書的密碼,就是生成加密證書的那個密碼
這四個參數如何填寫,依據生成的證書實際情況來即可,下圖是我的配置:
打開postman控制台查看調用結果
b.通過java代碼調用
1 /** 2 * 發送 SSL POST 請求(HTTPS),JSON形式 3 * @param apiUrl API接口URL 4 * @param json JSON對象 5 * @return 6 */ 7 public static String doPostSSL(String apiUrl, Object json) { 8 CloseableHttpResponse response = null; 9 String httpStr = ""; 10 try { 11 response=doPostSSLRequest(apiUrl,json); 12 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { 13 httpStr = EntityUtils.toString(response.getEntity(), "UTF-8"); 14 } 15 } catch (Exception e) { 16 e.printStackTrace(); 17 } finally { 18 if (response != null) { 19 try { 20 EntityUtils.consume(response.getEntity()); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } 24 } 25 } 26 return httpStr; 27 } 28 29 /** 30 *@Author: pengshihao 31 *@Description:帶證書 32 *@Date: 2019/7/4 33 */ 34 public static CloseableHttpResponse doPostSSLRequest(String apiUrl, Object json) throws Exception{ 35 //注意PKCS12證書 36 KeyStore keyStore = KeyStore.getInstance("PKCS12"); 37 //指向你的證書的絕對路徑,帶着證書去訪問 38 FileInputStream instream = new FileInputStream(new File(Constant.SSL_CERT_PATH)); 39 //證書的密碼,創建證書時的密碼 40 keyStore.load(instream, Constant.SSL_CERT_PASSWORD.toCharArray()); 41 SSLContext sslContext = new SSLContextBuilder().loadKeyMaterial(keyStore,Constant.SSL_CERT_PASSWORD.toCharArray()).build(); 42 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, 43 new String[] { "TLSv1" },null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); 44 45 CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 46 47 HttpPost httpPost = new HttpPost(apiUrl); 48 httpPost.addHeader("Accept", "*/*"); 49 httpPost.setHeader("Connection","Keep-Alive"); 50 httpPost.setHeader("Content-Type","application/json;charset=utf-8"); 51 httpPost.setHeader("Host",Constant.DATA_BASE_HOST); 52 httpPost.addHeader("Cache-Control", "max-age=0"); 53 httpPost.setEntity(new StringEntity(json.toString(),"UTF-8"));//解決中文亂碼問題 54 55 return httpClient.execute(httpPost); 56 }
以上就是具體的實操。