JAVA基于HTTPS的加密远程调用的实现

原文地址:http://hi.baidu.com/sunjoe/blog/item/629daa3ef802edff828b13e6.html


ServicePlatform-v2最终通过HTTPS强加密实现了通讯的安全传输,毕竟传输的都是关键的用户账号,而且我们现在还没有自己的专有网络.
ServicePlatform 的RMI使用的轻量级Hessian或Burlup协议是基于HTTP协议的,如果部署得当,在没有恶意窃听的情况下是不会出现用户信息泄漏的问题, 但是一旦平台启用后RMI调用被恶意截获,那么在request里设计的注册服务的密码验证和用户信息都将实效,SP平台的接口暴露,用户信息泄漏. 彻底的解决方法就是强制HTTPS方式访问RMI接口,而HTTPS对于HTTP是协议本身是透明的(所谓嵌套层的由来),所以HTTPS可以在不修改 SP的代码的(配置描述符的修改当然避免不了)情况下实现RMI通讯的强加密,而且也确实如此.
ServicePlatform的默认部署是强制 HTTPS访问RMI接口,使用ServicePlatform的客户端除了需要配置为HTTPS的invoker的地址外,还需要获得 ServicePlatform的证书,否则JSSE将不会正常工作.下面是部署描述符web.xml里面强制HTTPS访问的部分.

<!-- comment below to enable plain HTTP invoker rather than HTTPS only -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>remote</web-resource-name>
        <url-pattern>/remote/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

ServicePlatform的证书的生成由Java的keygen完成
%JAVA_HOME%\bin\keytool -genkey -alias tomcat -keypass changeit -keyalg RSA -validity 365
其中,changeit为密钥的生成密码,需要与tomcat的服务器部署描述符一致,参考下面的server.xml的片断
<Connector port="8443" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" disableUploadTimeout="true"
               acceptCount="100" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" keystorePass="changeit" keystoreFile="conf/keystore"/>

生成证书时必须填写正确的ServicePlatform运行的服务器的FQDN,然后和service-dist.properties文件中的invoker的URL的FQDN完全一致,
否则JSSE将证书视为无效,无法建立HTTPS连接,然后在启动客户端的时候需要添加指向证书的JVM参数
-Djavax.net.ssl.trustStore=path\to\your\generated\keystore\file

应用服务器和JDK版本信息
Application Server tomcat-5.5.15
JDK 1.5.0_06

Java应用Tomcat中实现https安全连接的方法

SSL, 或者Secure Socket Layer,是一种允许web浏览器和web服务器通过一个安全的连接进行交流的技术。这意味着将被发送的数据在一端被翻译成密码,传送出去,然后在另一端解开密码,再进行处理。这是一个双向的过程,也就是浏览器和服务器都需要在发送数据之前对它们进行加密。

SSL协定的另一个重要方面是认证(Authentication)。这就是说,在你开始试图通过一个安全连接与一个web服务器交流的时候,这个服务器会要求你的浏览器出示一组证件,通过“鉴定”的方式来证明这就是你所声明的网站。

在某些情况下,服务器还会要求你的web浏览器的认证书,证明你就是你所说的那个人。这就是所知的“客户认证”,尽管实际情况中,更多地用在商务-对-商务(B2B)交易,而不是对个人用户。

但大多数有SSL功能的web服务器不要求客户认证(Client Authentication)。

证书

为了能实施SSL,一个web服务器对每个接受安全连接的外部接口(IP 地址)必须要有相应的证书(Certificate)。关于这个设计的理论是一个服务器必须提供某种合理的保证以证明这个服务器的主人就是你所认为的那个人。这个证书要陈述与这个网站相关联的公司,以及这个网站的所有者或系统管理员的一些基本联系信息。

这个证书由所有人以密码方式签字,其他人非常难伪造。对于进行电子商务(e-commerce)的网站,或其他身份认证至关重要的任何商业交易,认证书要向大家所熟知的认证权威(Certificate Authority (CA))如VeriSign或Thawte来购买。这样的证书可用电子技术证明属实。实际上,认证权威单位会担保它发出的认证书的真实性,如果你信任发出认证书的认证权威单位的话,你就可以相信这个认证书是行У摹?/font>

在许多情况下,认证并不是真正使人担忧的事。系统管理员或许只想要保证被服务器传送和接收的数据是秘密的,不会被连接线上的偷窃者盗窃到。庆幸的是,Java提供相对简单的被称为keytool的命令行工具,可以简单地产生“自己签名”的证书。自己签名的证书只是用户产生的证书,没有正式在大家所熟知的认证权威那里注册过,因此不能确保它的真实性。但却能保证数据传输的安全性。

认证也许很重要,也许不重要,完全决定于网站的需要。

用Tomcat来配置SSL主要有下面这么两大步骤:

一、生成证书

1、 在命令行下执行:

%Java_home%\bin\keytool -genkey -alias tomcat -keyalg RSA

在此命令中,keytool是JDK自带的产生证书的工具。把RSA运算法则作为主要安全运算法则,这保证了与其它服务器和组件的兼容性。

这个命令会在用户的home directory产生一个叫做" .keystore " 的新文件。在执行后,你首先被要求出示keystore密码。Tomcat使用的默认密码是" changeit "(全都是小写字母),如果你愿意,你可以指定你自己的密码。你还需要在server.xml配置文件里指定自己的密码,这在以后会有描述。

2、 你会被要求出示关于这个认证书的一般性信息,如公司,联系人名称,等等。这些信息会显示给那些试图访问你程序里安全网页的用户,以确保这里提供的信息与他们期望的相对应。

3、 你会被要求出示密钥(key)密码,也就是这个认证书所特有的密码(与其它的储存在同一个keystore文件里的认证书不同)。你必须在这里使用与keystore密码相同的密码。(目前,keytool会提示你按ENTER键会自动帮你做这些)。

如果一切顺利,你现在就拥有了一个可以被你的服务器使用的有认证书的keystore文件。

二、配置tomcat

第二个大步骤是把secure socket配置在$CATALINA_HOME/conf/server.xml文件里。$CATALINA_HOME代表安装Tomcat的目录。一个例子是SSL连接器的元素被包括在和Tomcat一起安装的缺省server.xml文件里。它看起来象是这样:

$CATALINA_HOME/conf/server.xml

< -- Define a SSL Coyote HTTP/1.1 Connector on port 8443 -->

< !--

< Connector

port="8443" minProcessors="5" maxProcessors="75"

enableLookups="true" disableUploadTimeout="true"

acceptCount="100" debug="0" scheme="https" secure="true";

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

-->

Connector元素本身,其默认形式是被注释掉的(commented out),所以需要把它周围的注释标志删除掉。然后,可以根据需要客户化(自己设置)特定的属性。一般需要增加一下keystoreFile和keystorePass两个属性,指定你存放证书的路径(如:keystoreFile="C:/.keystore")和刚才设置的密码(如:keystorePass="123456")。关于其它各种选项的详细信息,可查阅Server Configuration Reference。

在完成这些配置更改后,必须象重新启动Tomcat,然后你就可以通过SSL访问Tomcat支持的任何web应用程序。只不过指令需要像下面这样:https://localhost:8443

使用SSL构建安全的Socket

SSL(安全套接层)是Netscape公司在1994年开发的,最初用于WEB浏览器,为浏览器与服务器间的数据传递提供安全保障,提供了加密、来源认证和数据完整性的功能。现在SSL3.0得到了普遍的使用,它的改进版TLS(传输层安全)已经成为互联网标准。SSL本身和TCP套接字连接是很相似的,在协议栈中,SSL可以被简单的看作是安全的TCP连接,但是某些TCP连接的特性它是不支持的,比如带外数据(out-of-bound)。

在构建基于Socket的C/S程序时,通过添加对SSL的支持来保障数据安全和完整是不错的方法。完善的Java为我们提供了简单的实现方法:JSSE(Java安全套接字扩展)。JSSE是一个纯Java实现的SSL和TLS协议框架,抽象了SSL和TLS复杂的算法,使安全问题变得简单。JSSE已经成为J2SE1.4版本中的标准组件,支持SSL 3.0和TLS 1.0。我们将通过一个具体的例子演示JSSE的一些基本应用。例子中的服务器端将打开一个SSL Socket,只有持有指定证书的客户端可以与它连接,所有的数据传递都是加密的。

构造一个SSLSocket是非常简单的:

SSLServerSocketFactory factory=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(portNumber);
SSLSocket socket = (SSLSocket);

但是执行这样的程序会产生一个异常,报告找不到可信任的证书。SSLSocket和普通的Socket是不一样的,它需要一个证书来进行安全认证。

一、 证书

生成一个CA证书,在命令行下执行:

keytool –genkey –keystore SSLKey –keyalg rsa –alias SSL

黑体部分是用户可以自己指定的参数,第一个参数是要生成的证书的名字,第二个参数是证书的别名。rsa指明了我们使用的加密方法。

系统会要求输入证书发放者的信息,逐项输入即可,如下图:

  

    系统生成的文件命将会和证书名相同。证书可以提交给权威CA认证组织审核,如果通过审核,组织会提供信任担保,向客户担保你的连接是安全的。当然这不是必须的。在我们的例子中会把证书直接打包到客户端程序中,保证客户端是授权用户,避免伪造客户,所以不需要提交审核。

二、 服务器端

现在可以编写服务器端的代码,与普通的Socket代码不同,我们需要在程序中导入证书,并使用该证书构造SSLSocket。需要的说明的是:

●KeyStore ks=KeyStore.getInstance("JKS");

访问Java密钥库,JKS是keytool创建的Java密钥库,保存密钥。

● KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");

创建用于管理JKS密钥库的X.509密钥管理器。

● SSLContext sslContext=SSLContext.getInstance("SSLv3");

构造SSL环境,指定SSL版本为3.0,也可以使用TLSv1,但是SSLv3更加常用。

●sslContext.init(kmf.getKeyManagers(),null,null);

初始化SSL环境。第二个参数是告诉JSSE使用的可信任证书的来源,设置为null是从javax.net.ssl.trustStore中获得证书。第三个参数是JSSE生成的随机数,这个参数将影响系统的安全性,设置为null是个好选择,可以保证JSSE的安全性。

完整代码如下:

/*
*SSL Socket的服务器端
*@Author Bromon
*/

package org.ec107.ssl;

import java.net.*;
import javax.net.ssl.*;
import java.io.*;
import java.security.*;

public class SSLServer
{
   static int port=8266;   //系统将要监听的端口号,82.6.6是偶以前女朋友的生日^_^
   static SSLServerSocket server;
  
   /*
   *构造函数
   */
  
   public SSLServer()
   {
   
   }
  
  
   /*
   *@param port 监听的端口号
   *@return 返回一个SSLServerSocket对象
   */
  
   private static SSLServerSocket getServerSocket(int thePort)
   {
    SSLServerSocket s=null;
    try
    {
     String key="SSLKey";   //要使用的证书名

     char keyStorePass[]="12345678".toCharArray();   //证书密码

     char keyPassword[]="12345678".toCharArray();   //证书别称所使用的主要密码

     KeyStore ks=KeyStore.getInstance("JKS");   //创建JKS密钥库

     ks.load(new FileInputStream(key),keyStorePass);

     //创建管理JKS密钥库的X.509密钥管理器
     KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");

     kmf.init(ks,keyPassword);

     SSLContext sslContext=SSLContext.getInstance("SSLv3");

     sslContext.init(kmf.getKeyManagers(),null,null);
  
     //根据上面配置的SSL上下文来产生SSLServerSocketFactory,与通常的产生方法不同
     SSLServerSocketFactory factory=sslContext.getServerSocketFactory();

     s=(SSLServerSocket)factory.createServerSocket(thePort);

    }catch(Exception e)
    {
     System.out.println(e);
    }
    return(s);
   }
  
  
   public static void main(String args[])
   {
    try
    {
     server=getServerSocket(port);
     System.out.println("在”+port+”端口等待连接...");

     while(true)
     {
      SSLSocket socket=(SSLSocket)server.accept();
     
      //将得到的socket交给CreateThread对象处理,主线程继续监听
      new CreateThread(socket);
     
     }
    }catch(Exception e)
    {
     System.out.println("main方法错误80:"+e);
    }
   }
}

/*
*内部类,获得主线程的socket连接,生成子线程来处理
*/

class CreateThread extends Thread
{
   static BufferedReader in;
   static PrintWriter out;
   static Socket s;
  
   /*
   *构造函数,获得socket连接,初始化in和out对象
   */
  
   public CreateThread(Socket socket)
   {
    try
    {
     s=socket;
     in=new BufferedReader(new InputStreamReader(s.getInputStream(),"gb2312"));

     out=new PrintWriter(s.getOutputStream(),true);

     start();   //开新线程执行run方法

    }catch(Exception e)
    {
     System.out.println(e);
    }
   
   }
  
   /*
   *线程方法,处理socket传递过来的数据
   */
  
   public void run()
   {
    try
    {
     String msg=in.readLine();
     System.out.println(msg);
     s.close();
    }catch(Exception e)
    {
     System.out.println(e);
    }
   }
}

将我们刚才生成的证书放到程序所在的目录下,上面的代码就可以在编译之后执行:

java org.ec107.ssl.SSLServer

在8266端口等待连接…

三、 客户端

客户端的代码相对简单,我们可以不在程序中指定SSL环境,而是在执行客户端程序时指定。需要注意的是客户端并没有导入证书,而是采用了默认的工厂方法构造SSLSocket:

● SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();

构造默认的工厂方法

●Socket s=factory.createSocket("localhost",port);

打开一个SSLSocket连接

/*
*SSL Socket 的客户端
*@Author Bromon
*/

package org.ec107.ssl;

import java.net.*;
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;

public class SSLClient
{
   static int port=8266;
   public static void main(String args[])
   {
    try
    {
     SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();

     Socket s=factory.createSocket("localhost",port);
    
     PrintWriter out=new PrintWriter(s.getOutputStream(),true);
     out.println("安全的说你好");
     out.close();
     s.close();
    }catch(Exception e)
    {
     System.out.println(e);
    }
   }
}

把服务器产生的证书(SSLKey)拷贝到程序所在的目录,执行这个程序的时候需要向javax.net.ssl.trustStore环境变量传入证书名:

java –Djavax.net.ssl.trustStore=SSLKey org.ec107.ssl.SSLClient

可以在服务器的控制台看到客户端发送过来的数据。

执行客户端可以有另一种方法,把证书拷贝到java home/lib/security目录下,名字改为jssecacerts,然后可以直接执行客户端:

java org.ec107.ssl.SSLClient

程序会自动的到上述目录下去寻找jssecacerts文件作为默认的证书。需要注意的是这里的java home并不是我们在安装J2SE时指定的那个JAVA_HOME。可以执行一个程序来得到java home的位置:

public class GetJavaHome
{
    public static void main(String args[])
    {
      System.out.println(System.getProperty(“java.home”));
    }
}

一般情况下(windows 2K)hava home的位置是在C:Program FilesJavaj2re1.4.0_02,相对的,证书就应该拷贝到C:Program FilesJavaj2re1.4.0_02libsecurity下,如果安装了自带JDK的Java IDE,比如JBuilder,情况可能会有不同。

    如果程序客户在不持有证书的情况下直接进行连接,服务器端会产生运行时异常,不允许进行连接。

运行环境:windows 2K server,j2sdk1.4.1

你可能感兴趣的:(java,加密,exception,socket,ssl,服务器)