原文:http://qingkangxu.iteye.com/blog/1614053

《JSP和Servlet那些事儿 》系列文章旨在阐述Servlet(Struts和Spring的MVC架构基础)和JSP内部原理以及一些比较容易混淆的概念(比如forward和redirect区别、静态include和<jsp:include标签区别等)和使用,本文为系列文章之启蒙篇--初探HTTP服务器,基本能从本文中折射出Tomcat和Apache HTTPD等处理静态文件的原理。敬请关注连载!

前言

在前一篇博文中阐述了基于普通Socket的简化版HTTP服务器实现:http://qingkangxu.iteye.com/blog/1562033,在深入学习JSP和Servlet之前,HTTPS是一个很常见的名词,也许很多人听到HTTPS就有点害怕,至少我本人是比较害怕的,总觉得他非常的高深;其实就Java而言,如果希望以HTTPS的方式服务,本质就是在TCP连接之上加上基于SSL协议的握手,成功握手之后数据的接收和发送都是加密过的。大家熟知Tomcat,根据Tomcat的配置文档可以很容易地配置一个基于HTTPS的Connector,我们在server.xml做如下配置之后,8443这个Socket监听就必须使用HTTPS才能访问。

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"

maxThreads="150" scheme="https" secure="true"

keystoreFile="${catalina.home}/conf/keystore" keystorePass="changeit"

clientAuth="false" sslProtocol="TLS" />

通过本文的阅读,希望你能知道为什么需要这样配置?配置是怎么被用于底层JDK安全相关API的?

一些SSL相关的基础概念

对称加密:就是接收方和发送方都使用相同的密钥,双方都必须知道这个密钥,其存在的问题就是当密钥被截取之后一切都变得不安全了。

非对称加密:就是加密和解密使用不同的密钥,缺点是因为算法复杂所以性能比对称加密低,但是安全性更高

keystore(证书库):看到store应该就能明白,其是一个库文件用于存储多个证书条目,没有证书条目有alias(别名)进行标识,一般需要安全的一方(比如服务器端)需要设置keystore配置

truststore(信任证书库):一般是客户端用于标识自己信任的通信源。

注意:无论是服务器端或者是客户端都可以设置自己的keystore和truststore,只不过一般都是客户端认证服务器端,服务器端很少需要认证客户端的;就像我们通过浏览器访问一个HTTPS的地址,有时候会被弹出是否信任证书,而我们很少提供自己的证书。

此外HTTPS不是单纯的使用对称加密或者是非对称加密,HTTPS在握手阶段使用非对称加密方式握手,双方最后协商出一个对称加密密钥,握手之后的数据传输使用对称加密算法。

最简单的基于SSL的Socket实现
  服务器端

实现代码如下,主要通过SSLServerSocketFactory. getDefault()方法获取到创建Server端的SSLServerSocket工厂类,然后创建相应的SSLServerSocket监听,接收客户端的SSL Socket连接。接收到来自客户端的基于SSL的Socket连接之后就从Socket中获取到输入输出流用于和客户端交互。

package security.ssl;

  1. import java.io.BufferedReader;
  2. import java.io.InputStream;
  3. import java.io.InputStreamReader;
  4. import javax.net.ssl.SSLServerSocket;
  5. import javax.net.ssl.SSLServerSocketFactory;
  6. import javax.net.ssl.SSLSocket;
  7. /**
  8. *
  9. * Execute command: java -Djavax.net.ssl.keyStore=./server.key
  10. * -Djavax.net.ssl.keyStorePassword=changeit security.ssl.EchoServer
  11. *
  12. */
  13. public class EchoServer {
  14. public static void main(String[] args) {
  15. try {
  16. /**
  17. * Get the default SSLServerSocketFactory, it will use the default
  18. * default key manager(could be configured by javax.net.ssl.keyStore
  19. * and javax.net.ssl.keyStorePassword properties) and default trust
  20. * manager(could be configured by javax.net.ssl.trustStore and
  21. * javax.net.ssl.trustStorePassword properties)
  22. */
  23. SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory
  24. .getDefault();
  25. /**
  26. * Create the ServerSocket for receiving connection from client, ,
  27. * it roughly the same as non-ssl serversocket
  28. */
  29. SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory
  30. .createServerSocket(9999);
  31. System.out.println("Ready to Receive...");
  32. SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();
  33. InputStream inputstream = sslsocket.getInputStream();
  34. InputStreamReader inputstreamreader = new InputStreamReader(
  35. inputstream);
  36. BufferedReader bufferedreader = new BufferedReader(
  37. inputstreamreader);
  38. String string = null;
  39. while ((string = bufferedreader.readLine()) != null) {
  40. System.out.println(string);
  41. System.out.flush();
  42. }
  43. System.out.println("End to Receive.");
  44. } catch (Exception exception) {
  45. exception.printStackTrace();
  46. }
  47. }
  48. }

客户端

客户端实现代码如下,主要通过SSLSocketFactory.getDefault()获取到创建Client端的SSLSocket工厂类,然后直接获取和Server端的SSLSocket连接。客户端从基于SSL的Socket连接获取到输入输出流用于和服务器端交互。

package security.ssl;

  1. import java.io.BufferedReader;
  2. import java.io.BufferedWriter;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5. import java.io.OutputStream;
  6. import java.io.OutputStreamWriter;
  7. import javax.net.ssl.SSLSocket;
  8. import javax.net.ssl.SSLSocketFactory;
  9. /**
  10. *
  11. * Execute command: java -Djavax.net.ssl.trustStore=./clientca.key
  12. * -Djavax.net.ssl.trustStorePassword=changeit security.ssl.EchoClient
  13. *
  14. */
  15. public class EchoClient {
  16. public static void main(String[] args) {
  17. try {
  18. /**
  19. * Get the default SSLSocketFactory, it will use the default
  20. * default key manager(could be configured by javax.net.ssl.keyStore
  21. * and javax.net.ssl.keyStorePassword properties) and default trust
  22. * manager(could be configured by javax.net.ssl.trustStore and
  23. * javax.net.ssl.trustStorePassword properties)
  24. */
  25. SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory
  26. .getDefault();
  27. /**
  28. * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket
  29. */
  30. SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(
  31. "localhost", 9999);
  32. InputStream inputstream = System.in;
  33. InputStreamReader inputstreamreader = new InputStreamReader(
  34. inputstream);
  35. BufferedReader bufferedreader = new BufferedReader(
  36. inputstreamreader);
  37. OutputStream outputstream = sslsocket.getOutputStream();
  38. OutputStreamWriter outputstreamwriter = new OutputStreamWriter(
  39. outputstream);
  40. BufferedWriter bufferedwriter = new BufferedWriter(
  41. outputstreamwriter);
  42. System.out.println("Please input the message...");
  43. String string = null;
  44. while ((string = bufferedreader.readLine()) != null) {
  45. bufferedwriter.write(string + '\n');
  46. bufferedwriter.flush();
  47. }
  48. } catch (Exception exception) {
  49. exception.printStackTrace();
  50. }
  51. }
  52. }

不管是Server端的实现还是客户端的实现,和普通 (非SSL) 的Socket监听和连接并没有太大的变化。之所以可以这么简单的实现基于SSL的socket通信,是因为JDK本身提供了很多的默认行为,比如对证书库的管理,底层SSL握手处理等。

测试以上代码

这里对Keystore 概念再次进行说明

***证书库(Keystore):这个包含了服务器端/客户端用于存储自己的私钥/信任证书的证书库,可以使用JDK提供的keytool工具辅助生成或修改。本例中我们不考虑服务器端要求验证客户端的情况(实际环境中较少应用),因此我们只需要生成服务器端用于存储私钥的keystore和客户端用于存储自己信任证书的keystore文件。

***服务器端用keystore制作:

使用以下命令可以生成一个用于服务器端的证书库(其保存了服务器的私钥):

keytool -genkey -keyalg RSA -alias server -dname "CN=SSLServer, OU=Dev, O=ECS, L=BeiJing, ST=BeiJing, C=CN" -keystore server.key -storepass changeit -keypass changeit

-genkey就是生成keystore的专用命令,-keyalg是加密算法,因为一个证书库可以保存很多个证书条目,因此我们每生成一个证书条目的时候都需要指定一个-alias用于唯一标识,dname标识证书发行者,-keystore是keystore文件路径,注意因为测试用的是自签名证书,因此最好设置相同的-storepass和-keypass。

***客户端用证书制作:

客户端信任的证书需要从服务器端的keystore做导出工作,以下命令先从服务器端的keystore文件中导出其证书(存储了服务器端的公钥),然后把证书导入到客户端的keystore文件中(此时客户端的这个keystore只包含信任证书)

keytool -export -file server.cer -alias server -keystore server.key -storepass changeit

keytool -import -alias serverKey -file server.cer -keystore clientca.key -storepass changeit -noprompt

以上证书生成之后,使用如下命令运行服务器端和客户端程序,注意根据实际情况调整keyStore和trustStore文件路径。

java -Djavax.net.ssl.keyStore=./server.key -Djavax.net.ssl.keyStorePassword=changeit security.ssl.EchoServer运行服务器端监听

java -Djavax.net.ssl.trustStore=./clientca.key -Djavax.net.ssl.trustStorePassword=changeit security.ssl.EchoClient运行客户端程序试图与服务器端进行SSL通信。

简单SSLSocket测试程序总结

以上是简单的基于SSL通信的Socket测试程序,而对于Tomcat这样的Web容器或者是包含EJB容器、JMS服务等大型的JavaEE应用服务器而言,单靠设置系统属性(javax.net.ssl.keyStore、javax.net.ssl.trustStore等)很难达到安全设置标准,因为往往不同的程序或者说不同的监听器需要不同的keysotre管理机制,对于此类情况,参照JDK Documentation中关于SSL Socket相关API依赖关系,可以很容易去实现不同监听器使用不同的keysotre。

上图为Java提供的与SSL连接相关的类依赖图,而在本章的最开始给出的例子中知道JDK提供了默认的SSLServerSocketFactory和SSLSocketFactory,因此我们没有使用到图中稍显复杂的这些类。不过,仔细分析,发现这个类图依赖关系实际上也很清晰。如果把目光聚集在中间的SSLContext类上就大大简化了我们的理解。首先往下是SSLContext可以创建SocketFactory,往上可以看出SSLContext可以使用自己的KeyManager(可理解为私钥证书管理器)和TrustManager(可理解为信任证书管理器),而相应的Manager均有其工厂类,KeyStoreManagerFactory和TrsutStoreManagerFactory均可传递keystore配置(图中的Key Material就是某一个实际的keystore文件)。

Tomcat的HTTPS监听就是使用了上面的API为每个Connector单独配置证书的,如果我们不希望通过系统属性,而是更灵活的配置相关的SSLSocket工厂,可以参照如下

自定义用于创建基于SSL的socket工厂类

基于对前面章节的掌握,大家应该知道,对于服务器端的Socket操作,主要通javax.net.ssl.SSLServerSocketFactory进行,客户端主要通过javax.net.ssl.SSLSocketFactory。

以下是扩展了Tomcat用于配置HTTPS Connector的Socket工厂类(MyJSSESocketFactory),同时支持服务器端和客户端的工厂类实现,大体思路是参照了上面的类图,最重要的代码就是下面几句

// Create and init SSLContext

  1. SSLContext context = SSLContext.getInstance(protocol);
  2. context
  3. .init(getKeyManagers(keystoreType, keystoreProvider, algorithm,
  4. (String) properties.getProperty(KEY_KEY_ALIAS)),
  5. null, new SecureRandom());
  6. // 用于Server端的ServerSocketFactory获取
  7. serverSslProxy = context.getServerSocketFactory();
  8. // 用于Client端的SocketFactory获取
  9. clientSslProxy = context.getSocketFactory();

1,  必须通过指定协议(默认TLS)获得一个SSLContext实例

2,  通过KeyManager和TrustManager初始化SSLContext实例,而KeyManager和TrustManager又是借助于指定的keystore和truststore文件进行初始化

3,  从SSLContext实例实例中获得用于新建ServerSocket(服务器端)和Socket(客户端)的相关工厂类

4, 有了以上三步操作,Server端进程和客户端进程就可以使用MyJSSESocketFactory创建ServerSocket和Socket

具体的实现类如下,这个类基本就是Tomcat配置HTTPS相关属性的缩减版,Tomcat的监听Socket就是通过该类类似的createSocket方法,由SSLServerSocketFactory工厂所创建。

package security.ssl;

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.net.InetAddress;
  7. import java.net.ServerSocket;
  8. import java.net.Socket;
  9. import java.net.SocketException;
  10. import java.security.KeyStore;
  11. import java.security.SecureRandom;
  12. import java.util.Properties;
  13. import javax.net.ssl.KeyManager;
  14. import javax.net.ssl.KeyManagerFactory;
  15. import javax.net.ssl.SSLContext;
  16. import javax.net.ssl.SSLException;
  17. import javax.net.ssl.SSLServerSocketFactory;
  18. import javax.net.ssl.SSLSocket;
  19. import javax.net.ssl.SSLSocketFactory;
  20. import javax.net.ssl.TrustManager;
  21. import javax.net.ssl.TrustManagerFactory;
  22. import javax.net.ssl.X509KeyManager;
  23. public class MyJSSESocketFactory {
  24. /**
  25. * 用于Server端的ServerSocketFactory代理
  26. */
  27. protected SSLServerSocketFactory serverSslProxy = null;
  28. /**
  29. * 用于Client端的SocketFactory代理
  30. */
  31. protected SSLSocketFactory clientSslProxy = null;
  32. protected boolean initialized;
  33. private String configFile = "ssl.properties";
  34. static String defaultProtocol = "TLS";
  35. static String defaultKeystoreType = "JKS";
  36. private static final String defaultKeystoreFile = ".keystore";
  37. private static final String defaultTruststoreFile = ".truststore";
  38. private static final String defaultKeyPass = "changeit";
  39. protected static Properties properties = new Properties();
  40. private static final String KEY_PREFIX = "ssl.";
  41. private static final String KEY_PROTOCOL = KEY_PREFIX + "protocol";
  42. private static final String KEY_ALGORITHM = KEY_PREFIX + "algorithm";
  43. private static final String KEY_KEYSTORE = KEY_PREFIX + "keystore";
  44. private static final String KEY_KEY_ALIAS = KEY_PREFIX + "keystoreAlias";
  45. private static final String KEY_KEYSTORE_TYPE = KEY_PREFIX + "keystoreType";
  46. private static final String KEY_KEYSTORE_PROVIDER = KEY_PREFIX
  47. + "keystoreProvider";
  48. private static final String KEY_KEYPASS = KEY_PREFIX + "keypass";
  49. private static final String KEY_KEYSTORE_PASS = KEY_PREFIX + "keystorePass";
  50. private static final String KEY_TRUSTSTORE = KEY_PREFIX + "trustStore";
  51. private static final String KEY_TRUSTSTORE_TYPE = KEY_PREFIX
  52. + "truststoreType";
  53. private static final String KEY_TRUSTSTORE_PASS = KEY_PREFIX
  54. + "truststorePass";
  55. private static final String KEY_TRUSTSTORE_PROVIDER = KEY_PREFIX
  56. + "truststoreProvider";
  57. private static final String KEY_TRUSTSTORE_ALGORITHM = KEY_PREFIX
  58. + "truststoreAlgorithm";
  59. public MyJSSESocketFactory(){
  60. }
  61. public MyJSSESocketFactory(String configFile_){
  62. this.configFile = configFile_;
  63. }
  64. /**
  65. * 使用代理Factory创建ServerSocket
  66. */
  67. public ServerSocket createSocket(int port) throws Exception {
  68. if (!initialized)
  69. init();
  70. ServerSocket socket = serverSslProxy.createServerSocket(port);
  71. return socket;
  72. }
  73. /**
  74. * 使用代理Factory创建ServerSocket
  75. */
  76. public ServerSocket createSocket(int port, int backlog) throws Exception {
  77. if (!initialized)
  78. init();
  79. ServerSocket socket = serverSslProxy.createServerSocket(port, backlog);
  80. return socket;
  81. }
  82. /**
  83. * 使用代理Factory创建ServerSocket
  84. */
  85. public ServerSocket createSocket(int port, int backlog,
  86. InetAddress ifAddress) throws Exception {
  87. if (!initialized)
  88. init();
  89. ServerSocket socket = serverSslProxy.createServerSocket(port, backlog,
  90. ifAddress);
  91. return socket;
  92. }
  93. public Socket acceptSocket(ServerSocket socket) throws IOException {
  94. SSLSocket asock = null;
  95. try {
  96. asock = (SSLSocket) socket.accept();
  97. } catch (SSLException e) {
  98. throw new SocketException("SSL handshake error" + e.toString());
  99. }
  100. return asock;
  101. }
  102. /**
  103. * 客户端Socket建立
  104. */
  105. public Socket createSocket(String host,int port) throws Exception {
  106. if (!initialized)
  107. init();
  108. Socket socket = clientSslProxy.createSocket(host,port);
  109. return socket;
  110. }
  111. /**
  112. * 初始化:
  113. * 1,从configFile文件里边获取keystore相关配置(比如keystore和truststore路径、密码等信息)
  114. * 2,调用SSLContext.getInstance,使用指定的protocol(默认为TLS)获取SSLContext
  115. * 3, 构造KeyManager和TrustManager,并使用构造出来的Manager初始化第二步获取到的SSLContext
  116. * 4,从SSLContext获取基于SSL的SocketFactory
  117. */
  118. private void init() throws Exception {
  119. FileInputStream fileInputStream = null;
  120. try {
  121. File sslPropertyFile = new File(configFile);
  122. fileInputStream = new FileInputStream(sslPropertyFile);
  123. properties.load(fileInputStream);
  124. } catch (Exception e) {
  125. System.out.println("Because no "+ configFile +" config file, server will use default value.");
  126. } finally {
  127. try {
  128. fileInputStream.close();
  129. } catch (Exception e2) {
  130. }
  131. }
  132. String protocol = properties.getProperty(KEY_PROTOCOL);
  133. if(protocol == null || "".equals(protocol)){
  134. protocol = defaultProtocol;
  135. }
  136. // Certificate encoding algorithm (e.g., SunX509)
  137. String algorithm = (String) properties.getProperty(KEY_ALGORITHM);
  138. if (algorithm == null) {
  139. algorithm = KeyManagerFactory.getDefaultAlgorithm();
  140. }
  141. String keystoreType = (String) properties
  142. .getProperty(KEY_KEYSTORE_TYPE);
  143. if (keystoreType == null) {
  144. keystoreType = defaultKeystoreType;
  145. }
  146. String keystoreProvider = (String) properties
  147. .getProperty(KEY_KEYSTORE_PROVIDER);
  148. String trustAlgorithm = (String) properties
  149. .getProperty(KEY_TRUSTSTORE_ALGORITHM);
  150. if (trustAlgorithm == null) {
  151. trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
  152. }
  153. // Create and init SSLContext
  154. SSLContext context = SSLContext.getInstance(protocol);
  155. context
  156. .init(getKeyManagers(keystoreType, keystoreProvider, algorithm,
  157. (String) properties.getProperty(KEY_KEY_ALIAS)),
  158. null, new SecureRandom());
  159. // 用于Server端的ServerSocketFactory获取
  160. serverSslProxy = context.getServerSocketFactory();
  161. // 用于Client端的SocketFactory获取
  162. clientSslProxy = context.getSocketFactory();
  163. }
  164. /**
  165. * 获取KeyManagers,KeyManagers根据keystore文件进行初始化,以便Socket能够获取到相应的证书
  166. */
  167. protected KeyManager[] getKeyManagers(String keystoreType,
  168. String keystoreProvider, String algorithm, String keyAlias)
  169. throws Exception {
  170. KeyManager[] kms = null;
  171. String keystorePass = getKeystorePassword();
  172. /**
  173. * 先获取到Keystore对象之后,使用KeyStore对KeyManagerFactory进行初始化,
  174. * 然后从KeyManagerFactory获取KeyManagers
  175. */
  176. KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass);
  177. if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
  178. throw new IOException("No specified keyAlias[" + keyAlias + "]");
  179. }
  180. KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
  181. kmf.init(ks, keystorePass.toCharArray());
  182. kms = kmf.getKeyManagers();
  183. if (keyAlias != null) {
  184. if (defaultKeystoreType.equals(keystoreType)) {
  185. keyAlias = keyAlias.toLowerCase();
  186. }
  187. for (int i = 0; i < kms.length; i++) {
  188. kms[i] = new JSSEKeyManager((X509KeyManager) kms[i], keyAlias);
  189. }
  190. }
  191. return kms;
  192. }
  193. /**
  194. * 获取TrustManagers,TrustManagers根据truststore文件进行初始化,
  195. * 以便Socket能够获取到相应的信任证书
  196. */
  197. protected TrustManager[] getTrustManagers(String keystoreType,
  198. String keystoreProvider, String algorithm) throws Exception {
  199. TrustManager[] tms = null;
  200. /**
  201. * 先获取到Keystore对象之后,使用KeyStore对TrustManagerFactory进行初始化,
  202. * 然后从TrustManagerFactory获取TrustManagers
  203. */
  204. KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider);
  205. if (trustStore != null) {
  206. TrustManagerFactory tmf = TrustManagerFactory
  207. .getInstance(algorithm);
  208. tmf.init(trustStore);
  209. tms = tmf.getTrustManagers();
  210. }
  211. return tms;
  212. }
  213. /**
  214. * 主要是调用getStore方法,传入keystore文件以供getStore方法解析.
  215. */
  216. protected KeyStore getKeystore(String type, String provider, String pass)
  217. throws IOException {
  218. String keystoreFile = (String) properties.getProperty(KEY_KEYSTORE);
  219. if (keystoreFile == null)
  220. keystoreFile = defaultKeystoreFile;
  221. try {
  222. return getStore(type, provider, keystoreFile, pass);
  223. } catch (FileNotFoundException fnfe) {
  224. throw fnfe;
  225. } catch (IOException ioe) {
  226. throw ioe;
  227. }
  228. }
  229. /**
  230. * keystore相关的keyPass和storePass密码.
  231. */
  232. protected String getKeystorePassword() {
  233. String keyPass = (String) properties.get(KEY_KEYPASS);
  234. if (keyPass == null) {
  235. keyPass = defaultKeyPass;
  236. }
  237. String keystorePass = (String) properties.get(KEY_KEYSTORE_PASS);
  238. if (keystorePass == null) {
  239. keystorePass = keyPass;
  240. }
  241. return keystorePass;
  242. }
  243. /**
  244. * 主要是调用getStore方法,传入truststore文件以供getStore方法解析.
  245. */
  246. protected KeyStore getTrustStore(String keystoreType,
  247. String keystoreProvider) throws IOException {
  248. KeyStore trustStore = null;
  249. /**
  250. * truststore文件优先级:指定的KEY_TRUSTSTORE属性->系统属性->当前路径<.truststore>文件
  251. */
  252. String truststoreFile = (String) properties.getProperty(KEY_TRUSTSTORE);
  253. if (truststoreFile == null) {
  254. truststoreFile = System.getProperty("javax.net.ssl.trustStore");
  255. }
  256. if (truststoreFile == null) {
  257. truststoreFile = defaultTruststoreFile;
  258. }
  259. /**
  260. * truststorePassword设置
  261. */
  262. String truststorePassword = (String) properties
  263. .getProperty(KEY_TRUSTSTORE_PASS);
  264. if (truststorePassword == null) {
  265. truststorePassword = System
  266. .getProperty("javax.net.ssl.trustStorePassword");
  267. }
  268. if (truststorePassword == null) {
  269. truststorePassword = getKeystorePassword();
  270. }
  271. /**
  272. *  trustStoreType设置优先级:指定的KEY_TRUSTSTORE_TYPE属性
  273. *  ->系统属性javax.net.ssl.trustStoreType
  274. *  -><keystoreType>
  275. */
  276. String truststoreType = (String) properties
  277. .getProperty(KEY_TRUSTSTORE_TYPE);
  278. if (truststoreType == null) {
  279. truststoreType = System.getProperty("javax.net.ssl.trustStoreType");
  280. }
  281. if (truststoreType == null) {
  282. truststoreType = keystoreType;
  283. }
  284. /**
  285. *  trustStoreType设置优先级:指定的KEY_TRUSTSTORE_PROVIDER属性
  286. *  ->系统属性javax.net.ssl.trustStoreProvider
  287. *  -><keystoreProvider>
  288. */
  289. String truststoreProvider = (String) properties
  290. .getProperty(KEY_TRUSTSTORE_PROVIDER);
  291. if (truststoreProvider == null) {
  292. truststoreProvider = System
  293. .getProperty("javax.net.ssl.trustStoreProvider");
  294. }
  295. if (truststoreProvider == null) {
  296. truststoreProvider = keystoreProvider;
  297. }
  298. /**
  299. * 通过调用getStore方法获取到keystore对象(也就是truststore对象)
  300. */
  301. if (truststoreFile != null) {
  302. try {
  303. trustStore = getStore(truststoreType, truststoreProvider,
  304. truststoreFile, truststorePassword);
  305. } catch (FileNotFoundException fnfe) {
  306. throw fnfe;
  307. } catch (IOException ioe) {
  308. if (truststorePassword != null) {
  309. try {
  310. trustStore = getStore(truststoreType,
  311. truststoreProvider, truststoreFile, null);
  312. ioe = null;
  313. } catch (IOException ioe2) {
  314. ioe = ioe2;
  315. }
  316. }
  317. if (ioe != null) {
  318. throw ioe;
  319. }
  320. }
  321. }
  322. return trustStore;
  323. }
  324. /**
  325. * 使用KeyStore的API从指定的keystore文件中构造出KeyStore对象,KeyStore对象用于初始化KeystoreManager和TrustManager.
  326. */
  327. private KeyStore getStore(String type, String provider, String path,
  328. String pass) throws IOException {
  329. KeyStore ks = null;
  330. InputStream istream = null;
  331. try {
  332. if (provider == null) {
  333. ks = KeyStore.getInstance(type);
  334. } else {
  335. ks = KeyStore.getInstance(type, provider);
  336. }
  337. if (!("PKCS11".equalsIgnoreCase(type) || "".equalsIgnoreCase(path))) {
  338. File keyStoreFile = new File(path);
  339. istream = new FileInputStream(keyStoreFile);
  340. }
  341. char[] storePass = null;
  342. if (pass != null && !"".equals(pass)) {
  343. storePass = pass.toCharArray();
  344. }
  345. ks.load(istream, storePass);
  346. } catch (FileNotFoundException fnfe) {
  347. throw fnfe;
  348. } catch (IOException ioe) {
  349. throw ioe;
  350. } catch (Exception ex) {
  351. throw new IOException(ex);
  352. } finally {
  353. if (istream != null) {
  354. try {
  355. istream.close();
  356. } catch (IOException ioe) {
  357. // Do nothing
  358. }
  359. }
  360. }
  361. return ks;
  362. }
  363. }

package security.ssl;

  1. import java.net.Socket;
  2. import java.security.Principal;
  3. import java.security.PrivateKey;
  4. import java.security.cert.X509Certificate;
  5. import javax.net.ssl.X509KeyManager;
  6. /**
  7. * 本类只作为一个包装类:主要是根据指定的别名(alias)获取证书和私钥等信息
  8. *
  9. */
  10. public final class JSSEKeyManager implements X509KeyManager {
  11. private X509KeyManager delegate;
  12. private String serverKeyAlias;
  13. public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) {
  14. this.delegate = mgr;
  15. this.serverKeyAlias = serverKeyAlias;
  16. }
  17. public String chooseClientAlias(String[] keyType, Principal[] issuers,
  18. Socket socket) {
  19. return delegate.chooseClientAlias(keyType, issuers, socket);
  20. }
  21. public String chooseServerAlias(String keyType, Principal[] issuers,
  22. Socket socket) {
  23. return serverKeyAlias;
  24. }
  25. public X509Certificate[] getCertificateChain(String alias) {
  26. return delegate.getCertificateChain(alias);
  27. }
  28. public String[] getClientAliases(String keyType, Principal[] issuers) {
  29. return delegate.getClientAliases(keyType, issuers);
  30. }
  31. public String[] getServerAliases(String keyType, Principal[] issuers) {
  32. return delegate.getServerAliases(keyType, issuers);
  33. }
  34. public PrivateKey getPrivateKey(String alias) {
  35. return delegate.getPrivateKey(alias);
  36. }
  37. }

前面的EchoServer和EchoClient只需要很小的改动就能使用这个工厂类达到定制keytore克truststore的目的;其中EchoServer类需要把下面注释的语句删掉,使用没有注释的代码

//SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory

  1. //      .getDefault();
  2. //使用自定义的SocketFactory
  3. MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory();
  4. /**
  5. * Create the ServerSocket for receiving connection from client,
  6. * it roughly the same as non-ssl serversocket
  7. */
  8. //SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory
  9. //      .createServerSocket(9999);
  10. SSLServerSocket sslserversocket = (SSLServerSocket) myJSSESocketFactory.createSocket(9999);

EchoClient则同样做如下调整便可

//SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory

  1. //      .getDefault();
  2. //使用自定义的SocketFactory
  3. MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory();
  4. /**
  5. * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket
  6. */
  7. //SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(
  8. //      "localhost", 9999);
  9. SSLSocket sslsocket = (SSLSocket) myJSSESocketFactory.createSocket("localhost", 9999);

如果需要对修改后的代码进行测试,保证运行java的当前路径有《.keystore》和《.truststore》两个证书文件或者是自定义配置文件来指定keystore和truststore文件路径、密码等信息;此外,需要保证.truststore是通过.keystore导出的证书生成的。

附件包含了测试证书及代码!

SSLSocket握手分析

以上完成了简单的基于SSL的Socket监听测试程序,以及自定义使用SSL的SocketFactory工厂类(基本雷同Tomcat的做法)。相比一般的Socket实现的HTTP监听,基于SSL Socket实现的HTTP监听,在接收到客户端的连接请求到真正交互数据之间有很多次握手动作,以使双方确认对方的身份。

上图为SSL握手操作图解,从Client端发起ClientHello请求之后到第13步握手完整状态之后双方才能进行数据传输,当然其中某些步骤是可选的。

在JDK中完成SSL握手的主要类为:sun.security.ssl.ServerHandshaker、sun.security.ssl.ClientHandshaker、sun.security.ssl.HandshakeMessage;其中,两个Handshaker分别是服务器和客户端使用的,在handshake的过程中传递的消息都是HandshakeMessage类的子类。以下只是象征性的对两个Message进行简单说明。

ClientHello

ClientHello是客户端发起握手的初始请求。可以看到其send方法发送了其支持的最大和最小SSL协议版本以及支持的加密集(Cipher Suite)等信息。

ClientHello(HandshakeInStream s, int messageLength) throws IOException {

  1. protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8());
  2. clnt_random = new RandomCookie(s);
  3. sessionId = new SessionId(s.getBytes8());
  4. cipherSuites = new CipherSuiteList(s);
  5. compression_methods = s.getBytes8();
  6. if (messageLength() != messageLength) {
  7. extensions = new HelloExtensions(s);
  8. }
  9. }
  10. void send(HandshakeOutStream s) throws IOException {
  11. s.putInt8(protocolVersion.major);
  12. s.putInt8(protocolVersion.minor);
  13. clnt_random.send(s);
  14. s.putBytes8(sessionId.getId());
  15. cipherSuites.send(s);
  16. s.putBytes8(compression_methods);
  17. extensions.send(s);
  18. }

CertificateMsg

该消息是双方都可能发送的,只要任意一方要求证书认证,那对方均需在握手中传递此消息。从send方法可看出,此消息包含了发送方所有的证书信息。

CertificateMsg(HandshakeInStream input) throws IOException {

  1. int chainLen = input.getInt24();
  2. List<Certificate> v = new ArrayList<Certificate>(4);
  3. CertificateFactory cf = null;
  4. while (chainLen > 0) {
  5. byte[] cert = input.getBytes24();
  6. chainLen -= (3 + cert.length);
  7. try {
  8. if (cf == null) {
  9. cf = CertificateFactory.getInstance("X.509");
  10. }
  11. v.add(cf.generateCertificate(new ByteArrayInputStream(cert)));
  12. } catch (CertificateException e) {
  13. throw (SSLProtocolException)new SSLProtocolException
  14. (e.getMessage()).initCause(e);
  15. }
  16. }
  17. chain = v.toArray(new X509Certificate[v.size()]);
  18. }
  19. void send(HandshakeOutStream s) throws IOException {
  20. s.putInt24(messageLength() - 3);
  21. for (byte[] b : encodedChain) {
  22. s.putBytes24(b);
  23. }
  24. }

JSP和Servlet那些事儿系列--HTTPS的更多相关文章

  1. 支持JSP和Servlet的Web服务器

    支持JSP和Servlet的Web服务器 1.Tomcat 服务器 目前非常流行的Tomcat服务器是Apache-Jarkarta开源项目中的一个子项目,是一个小型.轻量级的支持JSP和Servle ...

  2. MVC之前的那点事儿系列(7):WebActivator的实现原理详解

    文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在Http ...

  3. 转:jsp与servlet的区别与联系

    jsp与servlet的区别与联系 - gsyabc - 博客园https://www.cnblogs.com/sanyouge/p/7325656.html jsp和servlet的区别和联系:1. ...

  4. JSP 和 Servlet 的工作原理和生命周期

    JSP的英文名叫Java Server Pages,翻译为中文是Java服务器页面的意思,其底层就是一个简化的Servlet设计,是由sum公司主导参与建立的一种动态网页技术标准.Servlet 就是 ...

  5. jsp和servlet的区别联系

    jsp和servlet的区别联系 简单的说,SUN首先发展出SERVLET,其功能比较强劲,体系设计也很先进,只是,它输出HTML语句还是采用了老的CGI方式,是一句一句输出,所以,编写和修改HTML ...

  6. java语言体系的技术简介之JSP、Servlet、JDBC、JavaBean(Application)

    转自:https://zhangkunnan.iteye.com/blog/2040462 前言 Java语言 Java语言体系比较庞大,包括多个模块.从WEB项目应用角度讲有JSP.Servlet. ...

  7. JSP是Servlet详解

    前言:前一段时间写了好多Servlet和JSP相关的博客,自以为理解的差不多了,岂不知人外有人,天外有天,代码外还有源码,受高人点拨,看了一下Servlet源码,感触颇深,再也不敢说懂了,不明白生活的 ...

  8. 一、jsp和Servlet基础理论及jstl和EL表达式用法

    1.题外话:使用JSP有近一年半的时间了,但是开发量不大.昨天部门突然让做个读取EXCLE文件,然后在前台页面进行展示.并通过点击查看按钮可以对每条明细记录进行跳转后进行详情查看,并按照页面原型进行页 ...

  9. 6 年前,只会 JSP 和 Servlet 就可以找到工作

    这篇文章在去年就已经构思了,不过一直都没有整理出来,今天终于完成了这篇文章,所以发上来给大家看一看,都是一些个人的小感慨,我的观点可能不是非常的完善,大家也可以一起讨论. 找工作之难,难于上青天 五六 ...

随机推荐

  1. mybatis.5.动态SQL

    1.动态SQL,解决关联sql字符串的问题,mybatis的动态sql基于OGNL表达式 if语句,在DeptMapper.xml增加如下语句; <select id="selectB ...

  2. GridView中网络图片延迟加载导致高度计算失败的问题

    在使用下拉刷新以及加载更多控件的时候,出现了列表上滚不完的现象,经过半天的分析,最后得出结论:由于图片采用了延迟加载,导致列表按照没有加载图片时候的大小进行布局,相关的加载更多控件也就傻逼了. 最终解 ...

  3. Android源码批量下载及导入到Eclipse

    1.去http://code.google.com/p/msysgit/downloads/list  下载Git,进行安装 2.生成批量下载脚本文件  下载批量工具CreatAutoGetSh(工具 ...

  4. 解题:ZJOI 2015 幻想乡战略游戏

    题面 神**所有点都爆int,我还以为我写出什么大锅了,不开long long见祖宗... 动态点分治利用点分树树高不超过log的性质,我们对每个点维护一个子树和,一个点分树子树和,一个点分树上父亲的 ...

  5. 解题:POI 2006 PRO-Professor Szu

    题面 这个题是比较套路的做法啦,建反图后缩点+拓扑排序嘛,对于所有处在$size>=2$的SCC中的点都是无限解(可以一直绕) 然后注意统计的时候的小细节,因为无限解/大解也要输出,所以我们把这 ...

  6. 解题:SHOI 2012 回家的路

    题面 完了,做的时候已经想不起来分层图这个东西了QAQ 对于这种“多种”路径加中转站的题,还有那种有若干次“特殊能力”的题,都可以考虑用分层图来做 显然只需要记录所有的中转站+起点终点,然后拆出横竖两 ...

  7. Laravel 返回 JSON 格式

    第一种方法: 第一步.编写 BaseRequest首先我们需要构建一个 BaseRequest 来重写 Illuminate\Http\Request ,修改为默认优先使用 JSON 响应: app/ ...

  8. Ansible11:变量详解

    目录 简单说明 一.在Inventory中定义变量 二.在Playbook中定义变量 1.通过vars关键字定义 2.通过vars_files关键字引入变量文件 3.通过vars_prompt来实现人 ...

  9. [应用篇]第三篇 JSP 标准标签库(JSTL)总结

    有一种友谊叫做: "陪我去小卖部." "不去," "我请你" "走." 你想起了谁:胖先生?还有人陪你吗? JSP 标准 ...

  10. windows10安装ipython jupyter

    环境: windows 10 x64 python2.7(已经安装好numpy scipy matplotlib) 过程: 首先安装visual c++ 9.0环境,去http://aka.ms/vc ...