JSP和Servlet那些事儿系列--HTTPS
原文: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;
- 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)
- 下载次数: 43
JSP和Servlet那些事儿系列--HTTPS的更多相关文章
- 支持JSP和Servlet的Web服务器
支持JSP和Servlet的Web服务器 1.Tomcat 服务器 目前非常流行的Tomcat服务器是Apache-Jarkarta开源项目中的一个子项目,是一个小型.轻量级的支持JSP和Servle ...
- MVC之前的那点事儿系列(7):WebActivator的实现原理详解
文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在Http ...
- 转:jsp与servlet的区别与联系
jsp与servlet的区别与联系 - gsyabc - 博客园https://www.cnblogs.com/sanyouge/p/7325656.html jsp和servlet的区别和联系:1. ...
- JSP 和 Servlet 的工作原理和生命周期
JSP的英文名叫Java Server Pages,翻译为中文是Java服务器页面的意思,其底层就是一个简化的Servlet设计,是由sum公司主导参与建立的一种动态网页技术标准.Servlet 就是 ...
- jsp和servlet的区别联系
jsp和servlet的区别联系 简单的说,SUN首先发展出SERVLET,其功能比较强劲,体系设计也很先进,只是,它输出HTML语句还是采用了老的CGI方式,是一句一句输出,所以,编写和修改HTML ...
- java语言体系的技术简介之JSP、Servlet、JDBC、JavaBean(Application)
转自:https://zhangkunnan.iteye.com/blog/2040462 前言 Java语言 Java语言体系比较庞大,包括多个模块.从WEB项目应用角度讲有JSP.Servlet. ...
- JSP是Servlet详解
前言:前一段时间写了好多Servlet和JSP相关的博客,自以为理解的差不多了,岂不知人外有人,天外有天,代码外还有源码,受高人点拨,看了一下Servlet源码,感触颇深,再也不敢说懂了,不明白生活的 ...
- 一、jsp和Servlet基础理论及jstl和EL表达式用法
1.题外话:使用JSP有近一年半的时间了,但是开发量不大.昨天部门突然让做个读取EXCLE文件,然后在前台页面进行展示.并通过点击查看按钮可以对每条明细记录进行跳转后进行详情查看,并按照页面原型进行页 ...
- 6 年前,只会 JSP 和 Servlet 就可以找到工作
这篇文章在去年就已经构思了,不过一直都没有整理出来,今天终于完成了这篇文章,所以发上来给大家看一看,都是一些个人的小感慨,我的观点可能不是非常的完善,大家也可以一起讨论. 找工作之难,难于上青天 五六 ...
随机推荐
- mybatis.5.动态SQL
1.动态SQL,解决关联sql字符串的问题,mybatis的动态sql基于OGNL表达式 if语句,在DeptMapper.xml增加如下语句; <select id="selectB ...
- GridView中网络图片延迟加载导致高度计算失败的问题
在使用下拉刷新以及加载更多控件的时候,出现了列表上滚不完的现象,经过半天的分析,最后得出结论:由于图片采用了延迟加载,导致列表按照没有加载图片时候的大小进行布局,相关的加载更多控件也就傻逼了. 最终解 ...
- Android源码批量下载及导入到Eclipse
1.去http://code.google.com/p/msysgit/downloads/list 下载Git,进行安装 2.生成批量下载脚本文件 下载批量工具CreatAutoGetSh(工具 ...
- 解题:ZJOI 2015 幻想乡战略游戏
题面 神**所有点都爆int,我还以为我写出什么大锅了,不开long long见祖宗... 动态点分治利用点分树树高不超过log的性质,我们对每个点维护一个子树和,一个点分树子树和,一个点分树上父亲的 ...
- 解题:POI 2006 PRO-Professor Szu
题面 这个题是比较套路的做法啦,建反图后缩点+拓扑排序嘛,对于所有处在$size>=2$的SCC中的点都是无限解(可以一直绕) 然后注意统计的时候的小细节,因为无限解/大解也要输出,所以我们把这 ...
- 解题:SHOI 2012 回家的路
题面 完了,做的时候已经想不起来分层图这个东西了QAQ 对于这种“多种”路径加中转站的题,还有那种有若干次“特殊能力”的题,都可以考虑用分层图来做 显然只需要记录所有的中转站+起点终点,然后拆出横竖两 ...
- Laravel 返回 JSON 格式
第一种方法: 第一步.编写 BaseRequest首先我们需要构建一个 BaseRequest 来重写 Illuminate\Http\Request ,修改为默认优先使用 JSON 响应: app/ ...
- Ansible11:变量详解
目录 简单说明 一.在Inventory中定义变量 二.在Playbook中定义变量 1.通过vars关键字定义 2.通过vars_files关键字引入变量文件 3.通过vars_prompt来实现人 ...
- [应用篇]第三篇 JSP 标准标签库(JSTL)总结
有一种友谊叫做: "陪我去小卖部." "不去," "我请你" "走." 你想起了谁:胖先生?还有人陪你吗? JSP 标准 ...
- windows10安装ipython jupyter
环境: windows 10 x64 python2.7(已经安装好numpy scipy matplotlib) 过程: 首先安装visual c++ 9.0环境,去http://aka.ms/vc ...