- 浏览: 43607 次
- 性别:
- 来自: 北京
最新评论
-
宇落YL:
package com.bes.logging; import ...
JDK Logging模块深入分析 -
yysourcedownload:
看完之后,有收获,但是还是有很多,大方面的问题。
Logger ...
JDK Logging模块深入分析 -
在世界的中心呼喚愛:
dao4j一般的xml就可以解析了,好用在用它的api;sax ...
XML解析框架比较 -
Given_xing:
这样对比确实有些片面啊。。。不过看做是各种xml解析的示例,蛮 ...
XML解析框架比较 -
qingkangxu:
zfanxu 写道比较的太片面!DOM和SAX本身应用场景就不 ...
XML解析框架比较
《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;
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; /** * * Execute command: java -Djavax.net.ssl.keyStore=./server.key * -Djavax.net.ssl.keyStorePassword=changeit security.ssl.EchoServer * */ public class EchoServer { public static void main(String[] args) { try { /** * Get the default SSLServerSocketFactory, it will use the default * default key manager(could be configured by javax.net.ssl.keyStore * and javax.net.ssl.keyStorePassword properties) and default trust * manager(could be configured by javax.net.ssl.trustStore and * javax.net.ssl.trustStorePassword properties) */ SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory .getDefault(); /** * Create the ServerSocket for receiving connection from client, , * it roughly the same as non-ssl serversocket */ SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory .createServerSocket(9999); System.out.println("Ready to Receive..."); SSLSocket sslsocket = (SSLSocket) sslserversocket.accept(); InputStream inputstream = sslsocket.getInputStream(); InputStreamReader inputstreamreader = new InputStreamReader( inputstream); BufferedReader bufferedreader = new BufferedReader( inputstreamreader); String string = null; while ((string = bufferedreader.readLine()) != null) { System.out.println(string); System.out.flush(); } System.out.println("End to Receive."); } catch (Exception exception) { exception.printStackTrace(); } } }
客户端
客户端实现代码如下,主要通过SSLSocketFactory.getDefault()获取到创建Client端的SSLSocket工厂类,然后直接获取和Server端的SSLSocket连接。客户端从基于SSL的Socket连接获取到输入输出流用于和服务器端交互。
package security.ssl;
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; /** * * Execute command: java -Djavax.net.ssl.trustStore=./clientca.key * -Djavax.net.ssl.trustStorePassword=changeit security.ssl.EchoClient * */ public class EchoClient { public static void main(String[] args) { try { /** * Get the default SSLSocketFactory, it will use the default * default key manager(could be configured by javax.net.ssl.keyStore * and javax.net.ssl.keyStorePassword properties) and default trust * manager(could be configured by javax.net.ssl.trustStore and * javax.net.ssl.trustStorePassword properties) */ SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory .getDefault(); /** * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket */ SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket( "localhost", 9999); InputStream inputstream = System.in; InputStreamReader inputstreamreader = new InputStreamReader( inputstream); BufferedReader bufferedreader = new BufferedReader( inputstreamreader); OutputStream outputstream = sslsocket.getOutputStream(); OutputStreamWriter outputstreamwriter = new OutputStreamWriter( outputstream); BufferedWriter bufferedwriter = new BufferedWriter( outputstreamwriter); System.out.println("Please input the message..."); String string = null; while ((string = bufferedreader.readLine()) != null) { bufferedwriter.write(string + '\n'); bufferedwriter.flush(); } } catch (Exception exception) { exception.printStackTrace(); } } }
不管是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
SSLContext context = SSLContext.getInstance(protocol); context .init(getKeyManagers(keystoreType, keystoreProvider, algorithm, (String) properties.getProperty(KEY_KEY_ALIAS)), null, new SecureRandom()); // 用于Server端的ServerSocketFactory获取 serverSslProxy = context.getServerSocketFactory(); // 用于Client端的SocketFactory获取 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;
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.security.KeyStore; import java.security.SecureRandom; import java.util.Properties; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; public class MyJSSESocketFactory { /** * 用于Server端的ServerSocketFactory代理 */ protected SSLServerSocketFactory serverSslProxy = null; /** * 用于Client端的SocketFactory代理 */ protected SSLSocketFactory clientSslProxy = null; protected boolean initialized; private String configFile = "ssl.properties"; static String defaultProtocol = "TLS"; static String defaultKeystoreType = "JKS"; private static final String defaultKeystoreFile = ".keystore"; private static final String defaultTruststoreFile = ".truststore"; private static final String defaultKeyPass = "changeit"; protected static Properties properties = new Properties(); private static final String KEY_PREFIX = "ssl."; private static final String KEY_PROTOCOL = KEY_PREFIX + "protocol"; private static final String KEY_ALGORITHM = KEY_PREFIX + "algorithm"; private static final String KEY_KEYSTORE = KEY_PREFIX + "keystore"; private static final String KEY_KEY_ALIAS = KEY_PREFIX + "keystoreAlias"; private static final String KEY_KEYSTORE_TYPE = KEY_PREFIX + "keystoreType"; private static final String KEY_KEYSTORE_PROVIDER = KEY_PREFIX + "keystoreProvider"; private static final String KEY_KEYPASS = KEY_PREFIX + "keypass"; private static final String KEY_KEYSTORE_PASS = KEY_PREFIX + "keystorePass"; private static final String KEY_TRUSTSTORE = KEY_PREFIX + "trustStore"; private static final String KEY_TRUSTSTORE_TYPE = KEY_PREFIX + "truststoreType"; private static final String KEY_TRUSTSTORE_PASS = KEY_PREFIX + "truststorePass"; private static final String KEY_TRUSTSTORE_PROVIDER = KEY_PREFIX + "truststoreProvider"; private static final String KEY_TRUSTSTORE_ALGORITHM = KEY_PREFIX + "truststoreAlgorithm"; public MyJSSESocketFactory(){ } public MyJSSESocketFactory(String configFile_){ this.configFile = configFile_; } /** * 使用代理Factory创建ServerSocket */ public ServerSocket createSocket(int port) throws Exception { if (!initialized) init(); ServerSocket socket = serverSslProxy.createServerSocket(port); return socket; } /** * 使用代理Factory创建ServerSocket */ public ServerSocket createSocket(int port, int backlog) throws Exception { if (!initialized) init(); ServerSocket socket = serverSslProxy.createServerSocket(port, backlog); return socket; } /** * 使用代理Factory创建ServerSocket */ public ServerSocket createSocket(int port, int backlog, InetAddress ifAddress) throws Exception { if (!initialized) init(); ServerSocket socket = serverSslProxy.createServerSocket(port, backlog, ifAddress); return socket; } public Socket acceptSocket(ServerSocket socket) throws IOException { SSLSocket asock = null; try { asock = (SSLSocket) socket.accept(); } catch (SSLException e) { throw new SocketException("SSL handshake error" + e.toString()); } return asock; } /** * 客户端Socket建立 */ public Socket createSocket(String host,int port) throws Exception { if (!initialized) init(); Socket socket = clientSslProxy.createSocket(host,port); return socket; } /** * 初始化: * 1,从configFile文件里边获取keystore相关配置(比如keystore和truststore路径、密码等信息) * 2,调用SSLContext.getInstance,使用指定的protocol(默认为TLS)获取SSLContext * 3, 构造KeyManager和TrustManager,并使用构造出来的Manager初始化第二步获取到的SSLContext * 4,从SSLContext获取基于SSL的SocketFactory */ private void init() throws Exception { FileInputStream fileInputStream = null; try { File sslPropertyFile = new File(configFile); fileInputStream = new FileInputStream(sslPropertyFile); properties.load(fileInputStream); } catch (Exception e) { System.out.println("Because no "+ configFile +" config file, server will use default value."); } finally { try { fileInputStream.close(); } catch (Exception e2) { } } String protocol = properties.getProperty(KEY_PROTOCOL); if(protocol == null || "".equals(protocol)){ protocol = defaultProtocol; } // Certificate encoding algorithm (e.g., SunX509) String algorithm = (String) properties.getProperty(KEY_ALGORITHM); if (algorithm == null) { algorithm = KeyManagerFactory.getDefaultAlgorithm(); } String keystoreType = (String) properties .getProperty(KEY_KEYSTORE_TYPE); if (keystoreType == null) { keystoreType = defaultKeystoreType; } String keystoreProvider = (String) properties .getProperty(KEY_KEYSTORE_PROVIDER); String trustAlgorithm = (String) properties .getProperty(KEY_TRUSTSTORE_ALGORITHM); if (trustAlgorithm == null) { trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); } // Create and init SSLContext SSLContext context = SSLContext.getInstance(protocol); context .init(getKeyManagers(keystoreType, keystoreProvider, algorithm, (String) properties.getProperty(KEY_KEY_ALIAS)), null, new SecureRandom()); // 用于Server端的ServerSocketFactory获取 serverSslProxy = context.getServerSocketFactory(); // 用于Client端的SocketFactory获取 clientSslProxy = context.getSocketFactory(); } /** * 获取KeyManagers,KeyManagers根据keystore文件进行初始化,以便Socket能够获取到相应的证书 */ protected KeyManager[] getKeyManagers(String keystoreType, String keystoreProvider, String algorithm, String keyAlias) throws Exception { KeyManager[] kms = null; String keystorePass = getKeystorePassword(); /** * 先获取到Keystore对象之后,使用KeyStore对KeyManagerFactory进行初始化, * 然后从KeyManagerFactory获取KeyManagers */ KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass); if (keyAlias != null && !ks.isKeyEntry(keyAlias)) { throw new IOException("No specified keyAlias[" + keyAlias + "]"); } KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); kmf.init(ks, keystorePass.toCharArray()); kms = kmf.getKeyManagers(); if (keyAlias != null) { if (defaultKeystoreType.equals(keystoreType)) { keyAlias = keyAlias.toLowerCase(); } for (int i = 0; i < kms.length; i++) { kms[i] = new JSSEKeyManager((X509KeyManager) kms[i], keyAlias); } } return kms; } /** * 获取TrustManagers,TrustManagers根据truststore文件进行初始化, * 以便Socket能够获取到相应的信任证书 */ protected TrustManager[] getTrustManagers(String keystoreType, String keystoreProvider, String algorithm) throws Exception { TrustManager[] tms = null; /** * 先获取到Keystore对象之后,使用KeyStore对TrustManagerFactory进行初始化, * 然后从TrustManagerFactory获取TrustManagers */ KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider); if (trustStore != null) { TrustManagerFactory tmf = TrustManagerFactory .getInstance(algorithm); tmf.init(trustStore); tms = tmf.getTrustManagers(); } return tms; } /** * 主要是调用getStore方法,传入keystore文件以供getStore方法解析. */ protected KeyStore getKeystore(String type, String provider, String pass) throws IOException { String keystoreFile = (String) properties.getProperty(KEY_KEYSTORE); if (keystoreFile == null) keystoreFile = defaultKeystoreFile; try { return getStore(type, provider, keystoreFile, pass); } catch (FileNotFoundException fnfe) { throw fnfe; } catch (IOException ioe) { throw ioe; } } /** * keystore相关的keyPass和storePass密码. */ protected String getKeystorePassword() { String keyPass = (String) properties.get(KEY_KEYPASS); if (keyPass == null) { keyPass = defaultKeyPass; } String keystorePass = (String) properties.get(KEY_KEYSTORE_PASS); if (keystorePass == null) { keystorePass = keyPass; } return keystorePass; } /** * 主要是调用getStore方法,传入truststore文件以供getStore方法解析. */ protected KeyStore getTrustStore(String keystoreType, String keystoreProvider) throws IOException { KeyStore trustStore = null; /** * truststore文件优先级:指定的KEY_TRUSTSTORE属性->系统属性->当前路径<.truststore>文件 */ String truststoreFile = (String) properties.getProperty(KEY_TRUSTSTORE); if (truststoreFile == null) { truststoreFile = System.getProperty("javax.net.ssl.trustStore"); } if (truststoreFile == null) { truststoreFile = defaultTruststoreFile; } /** * truststorePassword设置 */ String truststorePassword = (String) properties .getProperty(KEY_TRUSTSTORE_PASS); if (truststorePassword == null) { truststorePassword = System .getProperty("javax.net.ssl.trustStorePassword"); } if (truststorePassword == null) { truststorePassword = getKeystorePassword(); } /** * trustStoreType设置优先级:指定的KEY_TRUSTSTORE_TYPE属性 * ->系统属性javax.net.ssl.trustStoreType * -><keystoreType> */ String truststoreType = (String) properties .getProperty(KEY_TRUSTSTORE_TYPE); if (truststoreType == null) { truststoreType = System.getProperty("javax.net.ssl.trustStoreType"); } if (truststoreType == null) { truststoreType = keystoreType; } /** * trustStoreType设置优先级:指定的KEY_TRUSTSTORE_PROVIDER属性 * ->系统属性javax.net.ssl.trustStoreProvider * -><keystoreProvider> */ String truststoreProvider = (String) properties .getProperty(KEY_TRUSTSTORE_PROVIDER); if (truststoreProvider == null) { truststoreProvider = System .getProperty("javax.net.ssl.trustStoreProvider"); } if (truststoreProvider == null) { truststoreProvider = keystoreProvider; } /** * 通过调用getStore方法获取到keystore对象(也就是truststore对象) */ if (truststoreFile != null) { try { trustStore = getStore(truststoreType, truststoreProvider, truststoreFile, truststorePassword); } catch (FileNotFoundException fnfe) { throw fnfe; } catch (IOException ioe) { if (truststorePassword != null) { try { trustStore = getStore(truststoreType, truststoreProvider, truststoreFile, null); ioe = null; } catch (IOException ioe2) { ioe = ioe2; } } if (ioe != null) { throw ioe; } } } return trustStore; } /** * 使用KeyStore的API从指定的keystore文件中构造出KeyStore对象,KeyStore对象用于初始化KeystoreManager和TrustManager. */ private KeyStore getStore(String type, String provider, String path, String pass) throws IOException { KeyStore ks = null; InputStream istream = null; try { if (provider == null) { ks = KeyStore.getInstance(type); } else { ks = KeyStore.getInstance(type, provider); } if (!("PKCS11".equalsIgnoreCase(type) || "".equalsIgnoreCase(path))) { File keyStoreFile = new File(path); istream = new FileInputStream(keyStoreFile); } char[] storePass = null; if (pass != null && !"".equals(pass)) { storePass = pass.toCharArray(); } ks.load(istream, storePass); } catch (FileNotFoundException fnfe) { throw fnfe; } catch (IOException ioe) { throw ioe; } catch (Exception ex) { throw new IOException(ex); } finally { if (istream != null) { try { istream.close(); } catch (IOException ioe) { // Do nothing } } } return ks; } }
package security.ssl;
import java.net.Socket; import java.security.Principal; import java.security.PrivateKey; import java.security.cert.X509Certificate; import javax.net.ssl.X509KeyManager; /** * 本类只作为一个包装类:主要是根据指定的别名(alias)获取证书和私钥等信息 * */ public final class JSSEKeyManager implements X509KeyManager { private X509KeyManager delegate; private String serverKeyAlias; public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) { this.delegate = mgr; this.serverKeyAlias = serverKeyAlias; } public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { return delegate.chooseClientAlias(keyType, issuers, socket); } public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { return serverKeyAlias; } public X509Certificate[] getCertificateChain(String alias) { return delegate.getCertificateChain(alias); } public String[] getClientAliases(String keyType, Principal[] issuers) { return delegate.getClientAliases(keyType, issuers); } public String[] getServerAliases(String keyType, Principal[] issuers) { return delegate.getServerAliases(keyType, issuers); } public PrivateKey getPrivateKey(String alias) { return delegate.getPrivateKey(alias); } }
前面的EchoServer和EchoClient只需要很小的改动就能使用这个工厂类达到定制keytore克truststore的目的;其中EchoServer类需要把下面注释的语句删掉,使用没有注释的代码
//SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory
// .getDefault(); //使用自定义的SocketFactory MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory(); /** * Create the ServerSocket for receiving connection from client, * it roughly the same as non-ssl serversocket */ //SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory // .createServerSocket(9999); SSLServerSocket sslserversocket = (SSLServerSocket) myJSSESocketFactory.createSocket(9999);
EchoClient则同样做如下调整便可
//SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory
// .getDefault(); //使用自定义的SocketFactory MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory(); /** * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket */ //SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket( // "localhost", 9999); 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 {
protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8()); clnt_random = new RandomCookie(s); sessionId = new SessionId(s.getBytes8()); cipherSuites = new CipherSuiteList(s); compression_methods = s.getBytes8(); if (messageLength() != messageLength) { extensions = new HelloExtensions(s); } } void send(HandshakeOutStream s) throws IOException { s.putInt8(protocolVersion.major); s.putInt8(protocolVersion.minor); clnt_random.send(s); s.putBytes8(sessionId.getId()); cipherSuites.send(s); s.putBytes8(compression_methods); extensions.send(s); }
CertificateMsg
该消息是双方都可能发送的,只要任意一方要求证书认证,那对方均需在握手中传递此消息。从send方法可看出,此消息包含了发送方所有的证书信息。
CertificateMsg(HandshakeInStream input) throws IOException {
int chainLen = input.getInt24(); List<Certificate> v = new ArrayList<Certificate>(4); CertificateFactory cf = null; while (chainLen > 0) { byte[] cert = input.getBytes24(); chainLen -= (3 + cert.length); try { if (cf == null) { cf = CertificateFactory.getInstance("X.509"); } v.add(cf.generateCertificate(new ByteArrayInputStream(cert))); } catch (CertificateException e) { throw (SSLProtocolException)new SSLProtocolException (e.getMessage()).initCause(e); } } chain = v.toArray(new X509Certificate[v.size()]); } void send(HandshakeOutStream s) throws IOException { s.putInt24(messageLength() - 3); for (byte[] b : encodedChain) { s.putBytes24(b); } }
- HttpsDemo.zip (7.3 KB)
- 下载次数: 47
相关推荐
在IT行业中,JSP(JavaServer Pages)和Servlet是用于构建动态Web应用程序的两种核心技术。本篇文章将深入探讨这两者以及它们与HTTP服务器之间的关系。首先,我们需要理解HTTP(超文本传输协议)的基础知识,它是...
Head First 系列书籍的特点是使用直观的方法教授复杂的概念,通过故事、图形和其他非传统的教学工具来提高学习效率。《Head First Servlet And JSP》一书旨在帮助读者深入理解并熟练掌握 Servlet 和 JSP 的使用技巧...
servlet,jsp,mysql,面向对象编程 二、系统功能 旅游网站设计主要用于实现旅游景点信息管理,基本功能包括:主界面模块设计,用户注册模块,旅游景点模块,酒店预订模块,后台管理模块等。本系统结构如下: (1)...
此项目为jsp制作的javaweb旅游网站项目,数据库使用mysql5。 技术介绍: 前端主要应用框架技术:jsp+jquery 后端主要应用框架技术:servlet+mysql 开发工具:Myeclipse或Eclipse、jdk1.8、tomcat8、mysql5.6或5.7...
《JSP servlet HEAD FIRST》这本书在IT领域尤其是Java Web开发界引起了广泛的关注和高度的赞誉。本书采用了一种独特而有效的教学方法,旨在帮助读者深入理解JSP(Java Server Pages)和Servlet技术,这两种技术是...
《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...
《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...
《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...
《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...
当JSP页面首次被请求时,会被转换成一个Servlet类,并根据需要编译和加载到服务器中。 JSP Web开发资源包通常包括JSP教程、示例代码、API文档、开发工具(如IDE插件)、框架(如Spring MVC、Struts等)以及相关的...
《Head First Servlets and JSP》第二版是一本深入浅出讲解Servlet和JSP技术的书籍,旨在帮助读者理解并掌握Java Web开发的基础知识。该书采用了一种独特的教学方法,通过轻松有趣的风格让学习过程更加高效和愉快。 ...
对于那些教授Servlet和JSP课程的教育者来说,如Philippe Maquet所述,他们曾经尝试过十多本不同的教科书,但最终发现只有HeadFirst系列的书籍能够满足他们的教学需求。这是因为HeadFirst系列书籍不仅内容全面,而且...
servlet,jsp,mysql,面向对象编程 二、系统功能 旅游网站设计主要用于实现旅游景点信息管理,基本功能包括:主界面模块设计,用户注册模块,旅游景点模块,酒店预订模块,后台管理模块等。本系统结构如下: (1)...
- **Head First Servlets and JSP 2nd Edition**:本书是Head First系列中的一个版本,专注于Servlets和JavaServer Pages (JSP)技术的学习。作为第二版,意味着在第一版的基础上进行了更新和完善,通常会包含更多的...
Apache作为一款广泛使用的Web服务器,而Tomcat则主要用于运行Java Servlet和JSP应用。在同一个系统中同时使用Apache和Tomcat,通常是为了利用Apache的高效静态内容处理能力以及Tomcat的动态Java应用处理能力。 ####...
在本项目"Servlet增删改查"中,我们将重点探讨如何利用Servlet实现一个基于MVC(Model-View-Controller)架构的Web应用程序,用于管理几米漫画的故事内容。 首先,让我们了解MVC模式。MVC是一种设计模式,它将应用...
实例61 JSP与Servlet程序的对比分析 实例62 一个简单的servlet 实例63 用servlet获取表单数据 实例64 用servlet读写文件数据 实例65 用servlet访问数据库 实例66 一个简单的struts应用实例——用户登录 第10章...
通过这个完整的JSP 2.0技术手册和源码,开发者不仅可以系统地学习JSP 2.0的核心概念和技术,还能通过实践加深理解,提升开发能力。在阅读手册的同时,结合源码分析和动手实践,将是深入掌握JSP 2.0的最佳方式。