这个东西困扰了我得有两个星期的时间。下面直接上过程吧。
一,制作证书
首先我们准备两个项目(没有问题可访问的)及两台Tomcat服务器(未经修改的)并测试相互访问(http请求即可) |
1,生成服务器证书库 |
keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore G:\ssl\server.keystore -dname "CN=127.0.0.1,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456 |
2,生成客户端证书库 |
keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore G:\ssl\client.p12 -dname "CN=client,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456 |
3,从客户端证书库中导出客户端证书 |
keytool -export -v -alias client -keystore G:\ssl\client.p12 -storetype PKCS12 -storepass 123456 -rfc -file G:\ssl\client.cer |
4,从服务器证书库中导出服务器证书 |
keytool -export -v -alias server -keystore G:\ssl\server.keystore -storepass 123456 -rfc -file G:\ssl\server.cer |
5,生成客户端信任证书库 |
keytool -import -v -alias server -file G:\ssl\server.cer -keystore G:\ssl\client.truststore -storepass 123456 |
6,将客户端证书导入到服务器证书库(使得服务器信任客户端证书) |
keytool -import -v -alias client -file G:\ssl\client.cer -keystore G:\ssl\server.keystore -storepass 123456 |
7,查看证书库中的全部证书 |
keytool -list -keystore G:\ssl\server.keystore |
二,配置Tomcat
三,项目演示
找个项目作为客户端发起请求(代码如下): |
package com.icesoft.client; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.security.KeyStore; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; public class HttpsClient { private static final String KEY_STORE_TYPE_JKS = "jks"; private static final String KEY_STORE_TYPE_P12 = "PKCS12"; private static final String SCHEME_HTTPS = "https"; private static final int HTTPS_PORT = 8443; private static final String HTTPS_URL = "https://127.0.0.1:8443/afinBusiness/admin/user/login"; private static final String KEY_STORE_CLIENT_PATH = "C:/Users/ASUS/Desktop/服务器/keytoolWorkSpace/apache-tomcat-8.0.15_01/key/client.p12"; private static final String KEY_STORE_TRUST_PATH = "C:/Users/ASUS/Desktop/服务器/keytoolWorkSpace/apache-tomcat-8.0.15_01/key/client.truststore"; private static final String KEY_STORE_PASSWORD = "123456"; private static final String KEY_STORE_TRUST_PASSWORD = "123456"; public static void main(String[] args) { try { ssl(); } catch (Exception e) { System.out.println("异常一"); e.printStackTrace(); } } private static void ssl() throws Exception { HttpClient httpClient = new DefaultHttpClient(); try { KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_JKS); InputStream ksIn = new FileInputStream(KEY_STORE_CLIENT_PATH); InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH)); try { keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); } finally { try { ksIn.close(); } catch (Exception ignore) {} try { tsIn.close(); } catch (Exception ignore) {} } SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore); Scheme sch = new Scheme(SCHEME_HTTPS, HTTPS_PORT, socketFactory); httpClient.getConnectionManager().getSchemeRegistry().register(sch); HttpGet httpget = new HttpGet(HTTPS_URL); System.out.println("executing request" + httpget.getRequestLine()); HttpResponse response = httpClient.execute(httpget); HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); String text; while ((text = bufferedReader.readLine()) != null) { System.out.println(text); } bufferedReader.close(); } EntityUtils.consume(entity); } finally { httpClient.getConnectionManager().shutdown(); } } } |
找个项目放在Tomcat_01服务器上当服务端:代码如下 |
import java.io.IOException; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; import com.qtong.afinance.core.domain.ResultObject; import com.qtong.afinance.core.util.HttpTool; import com.qtong.afinance.module.service.admin.AdminLoginService; @Controller @RequestMapping("/admin/user") public class AdminLoginController { @Autowired private AdminLoginService loginService; private static final long serialVersionUID=1601507150278487538L; private static final String ATTR_CER="javax.servlet.request.X509Certificate"; private static final String CONTENT_TYPE="text/plain;charset=UTF-8"; private static final String DEFAULT_ENCODING="UTF-8"; private static final String SCHEME_HTTPS="https"; @RequestMapping("/login") @ResponseBody public ResultObject login(HttpServletRequest req,HttpServletResponse resp) throws IOException{ System.out.println("已经访问到了,老子成功了"); System.out.println("无加工输出的请求为:"+req); String reqMess=HttpTool.javaProtogenesisGetRequest(req); System.out.println("加工后输出的请求为:"+reqMess); Map map=JSON.parseObject(reqMess,Map.class); String mobile=(String) map.get("mobile"); String password=(String) map.get("password"); System.out.println("在这里获取到的参数为>>>账号:"+mobile+"密码为:"+password); return null; } } |
客户端访问结果如下: |
 |
客户端的访问结果不重要,重要的是服务端的访问结果如何,如下: 看红框圈起来的部分,输出了内容,并且在这几句输出内容时没有报bug,那么则就说明此次的https请求是成功的 下面的bug是参数问题,我没有在客户端传递参数,但是服务端需要获取参数,所以出了bug。 |
 |
到这里大家是不是已经发现其实 只用了一台Tomcat服务器,和本文的主题不想符合,但是如果将客户端和服务端换下位置,1号服务器当客户端,2号服务器当服务端,那么则就与本文符合了那么一些(语文水平就这么高,凑合着看吧)。到这里服务之间的双向认证已经完成。不过在送给大家一端代码,可以直接放在服务端的方法里直接运行,如下:(查看证书的一段代码) |
import java.io.IOException; import java.io.PrintWriter; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; import com.qtong.afinance.core.domain.ResultObject; import com.qtong.afinance.core.util.HttpTool;
@Controller @RequestMapping("/admin/user") public class AdminLoginController { private static final long serialVersionUID=1601507150278487538L; private static final String ATTR_CER="javax.servlet.request.X509Certificate"; private static final String CONTENT_TYPE="text/plain;charset=UTF-8"; private static final String DEFAULT_ENCODING="UTF-8"; private static final String SCHEME_HTTPS="https"; @RequestMapping("/login") @ResponseBody public ResultObject login(HttpServletRequest req,HttpServletResponse resp) throws IOException{ resp.setContentType(CONTENT_TYPE); resp.setCharacterEncoding(DEFAULT_ENCODING); PrintWriter out = resp.getWriter(); X509Certificate[] certs=(X509Certificate[])req.getAttribute(ATTR_CER); if(certs!=null){ int count=certs.length; out.println("共检测到["+count+"]个客户端证书。。。"); System.out.println("************分割线************"); System.out.println("共检测到["+count+"]个客户端证书。。。"); for(int i=0; i< count; i++){ out.println("客户端证书["+(++i)+"]:"); out.println("校验结果:"+verifyCertificate(certs[--i])); out.println("证书详细:\r"+certs[i].toString()); System.out.println("************分割线************"); System.out.println("客户端证书["+(++i)+"]:"); System.out.println("校验结果:"+verifyCertificate(certs[--i])); System.out.println("证书详细:\r"+certs[i].toString()); } }else{ if(SCHEME_HTTPS.equalsIgnoreCase(req.getScheme())){ out.println("这是一个HTTPS请求,但是没有可用的客户端证书。。。"); System.out.println("************分割线************"); System.out.println("这是一个HTTPS请求,但是没有可用的客户端证书。。。"); }else{ out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表。。。"); System.out.println("************分割线************"); System.out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表。。。"); } } out.close(); System.out.println("已经访问到了,老子成功了"); System.out.println("无加工输出的请求为:"+req); String reqMess=HttpTool.javaProtogenesisGetRequest(req); System.out.println("加工后输出的请求为:"+reqMess); Map map=JSON.parseObject(reqMess,Map.class); String mobile=(String) map.get("mobile"); String password=(String) map.get("password"); System.out.println("在这里获取到的参数为>>>账号:"+mobile+"密码为:"+password); return null; } //校验证书是否过期 private boolean verifyCertificate(X509Certificate certificate){ boolean valid=true; try { certificate.checkValidity(); } catch (CertificateExpiredException e) { e.printStackTrace(); } catch (CertificateNotYetValidException e) { e.printStackTrace(); } return valid; } } |
下面是我参考资料帖子:这位博主很强 |
http://www.blogjava.net/icewee/archive/2012/06/04/379947.html 这个实现了浏览器与服务端的双向认证 http://www.blogjava.net/icewee/archive/2012/06/05/379983.html HTTPS请求代码的提供帖 |
这些是我在饱受折磨后得出来的结果在此分享给大家,貌似也没有什么,可是每一个成果从无到有的路途都不是一帆风顺的,祝大家项目上线必火,永无bug。
如有不全之处,还望大家多多海涵,不喜勿喷。