Java RMI从基础例子到使用securityManager再到ssl的RMI在到spring的RMI
什么是RMI,请参考看RMI远程调用部分。
额,很多人都说RMI没什么用,直接使用WebService就可以了。但是我觉得使用一些简单的,只用于自己开发的系列JAVA软件之间的远程调用的时候RMI还是具有自己的优势的,所以本人就做了一些研究,在网上参考了很多例子,想办法做到安全的RMI远程调用。
一、简单的例子,通过这个例子,首先可以使搭建一个简单的RMI的调用环境。
package rmiproj.server; import java.io.Serializable; public class Channel implements Serializable { /** * 用来测试的实体类,主要用来存放数据 */ private static final long serialVersionUID = 1L; private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Channel(int id, String name) { super(); this.id = id; this.name = name; } }
package rmiproj.server; import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List; /*这个接口是用来将远程端可以执行的方法暴露给客户端的,该接口必须继承Remote接口,并且接口中的每一个方法都必须抛出RemoteException*/ public interface ChannelManager extends Remote { public List<Channel> getChannels() throws RemoteException; }
package rmiproj.server; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @author Administrator * * 这个类是ChannelManager的实现类,同时继承了UnicastRemoteObject,根据我的理解, * 继承了UnicastRemoteObject以后,该类就可以直接绑定到Registry上暴露自己的相关行为了, * 不需要在使用UnicastRemoteObject中的exportObject来暴露了 * */ public class ChannelManagerImpl extends UnicastRemoteObject implements ChannelManager { private static final Object lock = new Object(); private static ChannelManager instance; protected ChannelManagerImpl() throws RemoteException { super(); } /** * 默认的id,因为继承了UnicastRemoteObject的原因,实现了Serializable接口 */ private static final long serialVersionUID = 1L; /* 使用单例模式来获得该类的对象 */ public static ChannelManager getInstance() throws RemoteException { if (instance == null) { synchronized (lock) { if (instance == null) instance = new ChannelManagerImpl(); } } return instance; } @Override public List<Channel> getChannels() throws RemoteException { List<Channel> channels = new ArrayList<Channel>(); channels.add(new Channel(1, "java")); channels.add(new Channel(2, "php")); channels.add(new Channel(3, "C")); channels.add(new Channel(4, "ASP")); System.out.println(new Date() + " getChannels method called!"); return channels; } }
package rmiproj.server; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; public class RMIServer { /** * @param args */ public static void main(String[] args) { System.out.println("RMI Server Starting..."); if (System.getSecurityManager() == null) { //这个方法是用来设置在远程调用的过程中使用的securityManager的,后面会用到 } try { //在1099端口上建立一个registry来供远程客户端调用,默认端口就是1099 LocateRegistry.createRegistry(1099); //把要暴露给远程客户端的实体对象绑定一个特定的名字以后,绑定到1099端口的Registry上 Naming.rebind("ChannelManager", ChannelManagerImpl.getInstance()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("RMI Server ready..."); } }
package rmiproj.client; import java.rmi.Naming; import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List; import rmiproj.server.Channel; import rmiproj.server.ChannelManager; public class ClientUtil { /** * @param args */ public static void main(String[] args) { // 这里的ChannelManager我使用的是服务器包里面的,在实际环境中,远程端要使用RMI,那么服务器端的接口远程段必须自己持有一份,这可能就是rmi的弊端之一 ChannelManager cm = (ChannelManager) ClientUtil .renewRMI("ChannelManager"); List<Channel> channels; try { channels = cm.getChannels(); for (int i = 0; i < channels.size(); i++) { Channel c = channels.get(i); System.out.println(c.getId() + "-" + c.getName()); } } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static Remote renewRMI(String name) { try { if (System.getSecurityManager() == null) { } // 就是在这里,通过Naming。lookup,在指定的服务端获得了远程调用对象 return Naming.lookup("rmi://127.0.0.1:1099/" + name); } catch (Exception re) { throw new IllegalStateException("bind " + name + " failure . "); } } }
服务器端启动:
客户端:
客户端调用后服务器端的信息:
到这里简单的例子就介绍完了,内容很简单,网上我也找到了很多这样的例子,就不多说了,我也是参考了别人的例子做出来。有这个例子,我们可以证实的一点就是,我们可以搭建RMI环境了。
二、在第二个例子中,加入一些安全措施,使用java的securityManager来做一些访问的限制。
项目的结构:
package rmiproj.myserver; import java.rmi.Remote; import java.rmi.RemoteException; /** 这个接口还是我们要暴露的远程方法的接口,同样的这个接口要继承Remote,并且接口中的所有方法都要抛出RemoteException */ public interface MyCalculater extends Remote { public long plus(long num1, long num2) throws RemoteException; public long minus(long num1, long num2) throws RemoteException; public long multi(long num1, long num2) throws RemoteException; public long division(long num1, long num2) throws RemoteException; }
package rmiproj.myserver; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; /** * MyCalculater的实现类,同样继承了UnicastRemoteObject * * */ public class MyCalculaterImpl extends UnicastRemoteObject implements MyCalculater { protected MyCalculaterImpl() throws RemoteException { super(); } /** * */ private static final long serialVersionUID = 1L; private static final Object lock = new Object(); private static MyCalculater instance; @Override public long plus(long num1, long num2) throws RemoteException { System.out.println(System.currentTimeMillis() + "服务器运行加法"); System.out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName()); return num1 + num2; } @Override public long minus(long num1, long num2) throws RemoteException { System.out.println(System.currentTimeMillis() + "服务器运行减法"); System.out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName()); return num1 - num2; } @Override public long multi(long num1, long num2) throws RemoteException { System.out.println(System.currentTimeMillis() + "服务器运行乘法"); System.out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName()); return num1 * num2; } @Override public long division(long num1, long num2) throws RemoteException { System.out.println(System.currentTimeMillis() + "服务器运行除法"); System.out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName()); return num1 / num2; } public static MyCalculater getInstance() throws RemoteException { if (null == instance) { synchronized (lock) { instance = new MyCalculaterImpl(); } } return instance; } }
package rmiproj.myserver; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; public class MyServer { public static void main(String[] args) { /** * 这里是这个server的重点,这里我们把系统的java.security.policy设置为我们自己定义的一个client.policy, * 在client。policy中我们定义了一个访问规则 * */ System.setProperty("java.security.policy", MyServer.class.getResource("client.policy").toString()); if (System.getSecurityManager() == null) { /* 使用了我们定义的policy文件以后,就要定义一个有效的SecurityManager来保证系统正确的应用了我们的规则 */ System.setSecurityManager(new SecurityManager()); } try { /* 这里和第一个例子一样,就是绑定我们提供的远程方法了 */ LocateRegistry.createRegistry(1099); Naming.rebind("cul", MyCalculaterImpl.getInstance()); System.out.println(Thread.currentThread().getName()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package rmiproj.myclient; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import rmiproj.myserver.MyCalculater; /** 用来测试的客户端 */ public class Test { public static void main(String[] args) { try { MyCalculater my = (MyCalculater) Naming .lookup("rmi://localhost:1099/" + "cul"); System.out.println(my.plus(1, 2)); Thread.sleep(5000); System.out.println(my.minus(1, 2)); Thread.sleep(5000); System.out.println(my.multi(2, 6)); Thread.sleep(5000); System.out.println(my.division(1, 2)); Thread.sleep(5000); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
测试结果:
服务器启动结果:
客户端测试结果:
(1)当policy文件的内容如上所示为
grant{
permission java.net.SocketPermission "*:1024-65535","accept,connect,listen";
};的时候表示的是允许任意ip的1024-65535端口进行socket的accept,connect,listen动作,这时候客户端肯定是可以调用方法的
可以看到在客户端我们成功的完成了四则运算。
同时在服务器端,我们也看到了调用的记录
这是我修改了policy文件的内容,内容如下
grant{
permission java.net.SocketPermission "192.168.1.210:1024-65535","accept,connect,listen";
}; 这时候只允许192.168.1.210来进行socket的各个动作了,这时候在进行测试
客户端:
异常了··nested exception``被服务器拒绝了,
服务器端:
服务器端也是这样的,在127.0.0.1:57304套接字处的链接被拒绝了
这时我修改一下客户端的测试代码,把localhost换成192.168.1.210,再进行测试
package rmiproj.myclient; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import rmiproj.myserver.MyCalculater; /** 用来测试的客户端 */ public class Test { public static void main(String[] args) { try { MyCalculater my = (MyCalculater) Naming .lookup("rmi://192.168.1.210:1099/" + "cul"); System.out.println(my.plus(1, 2)); Thread.sleep(5000); System.out.println(my.minus(1, 2)); Thread.sleep(5000); System.out.println(my.multi(2, 6)); Thread.sleep(5000); System.out.println(my.division(1, 2)); Thread.sleep(5000); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }测试结果
客户端:
客户端的测试是成功的,完成了四则运算。
服务端:
服务器端也打印了相应的记录哦。
三、简化调用过程?其实就是利用反射使用统一的excute方法来支持指定类中的方法
这里先讨论一下从别人那里参考过来的简化方法。
项目的结构如下,只看展开的就可以了
先看看服务端是个什么样子的吧。。。。。
package rmiproj.newserver; /* * 这个是一个操作接口,变化很大的,什么都不需要继承和扩展了,他出现的唯一的原因就是我要使用反射 * */ public interface MyCalculater { public long plus(long num1, long num2); public long minus(long num1, long num2); public long multi(long num1, long num2); public long division(long num1, long num2); }
package rmiproj.newserver; /*上面那个接口的实现类*/ public class MyCalculaterImpl implements MyCalculater { private static final long serialVersionUID = 1L; private static final Object lock = new Object(); private static MyCalculater instance; @Override public long plus(long num1, long num2) { System.out.println(System.currentTimeMillis() + "服务器运行加法"); return num1 + num2; } @Override public long minus(long num1, long num2) { System.out.println(System.currentTimeMillis() + "服务器运行减法"); return num1 - num2; } @Override public long multi(long num1, long num2) { System.out.println(System.currentTimeMillis() + "服务器运行乘法"); return num1 * num2; } @Override public long division(long num1, long num2) { System.out.println(System.currentTimeMillis() + "服务器运行除法"); return num1 / num2; } public static MyCalculater getInstance() { if (null == instance) { synchronized (lock) { instance = new MyCalculaterImpl(); } } return instance; } }
package rmiproj.newserver; import java.rmi.Remote; import java.rmi.RemoteException; /* * 我们要暴露的统一接口方法,继承了Remote,方法抛出了RemoteException * * 看看这个方法,第一个参数是要调用的service的名字,然后是方法的名字,然后就是要传入的参数 * 标准的反射调用 * * */ public interface CommonRmiService extends Remote { public Object execute(String serviceName, String methodName, Object[] args) throws RemoteException; }
package rmiproj.newserver; import java.lang.reflect.InvocationTargetException; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; /*就是上面的那个接口的实现类,继承了UnicastRemoteObject*/ public class CommonRmiServiceImpl extends UnicastRemoteObject implements CommonRmiService { protected CommonRmiServiceImpl() throws RemoteException { super(); } /** * */ private static final long serialVersionUID = 1L; @Override public Object execute(String serviceName, String methodName, Object[] args) throws RemoteException { //这段代码就是通过反射调用方法了,这个是反射的内容我就不多介绍了 Class<?> clazz = null; try { clazz = Class.forName(serviceName); } catch (ClassNotFoundException e1) { System.out.println(e1.getMessage()); } try { return ReflectUtil .execMethod(clazz.newInstance(), methodName, args); } catch (NoSuchMethodException e) { System.out.println(e.getMessage()); return null; } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); return null; } catch (IllegalAccessException e) { System.out.println(e.getMessage()); return null; } catch (InvocationTargetException e) { System.out.println(e.getMessage()); return null; } catch (InstantiationException e) { System.out.println(e.getMessage()); return null; } } }
这里面用到了一个ReflectUtil,也是早就写好的一个工具类了,做了一些精简,我就不多做注释了,代码如下
package rmiproj.newserver; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; public class ReflectUtil { public static Object execMethod(Object obj, String methodName, Object... params) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Object result = null; Class<?> c = obj.getClass(); Class<?>[] parameterTypes = new Class<?>[] {}; if (params != null) { parameterTypes = new Class<?>[params.length]; for (int i = 0; i < params.length; i++) { parameterTypes[i] = params[i].getClass(); }// for }// if Method m = getMethod(c, methodName, parameterTypes); result = m.invoke(obj, params); return result; } public static <T extends Object> Method getMethod(Class<T> clazz, String methodName, Class<?>[] parameterTypes) throws NoSuchMethodException { Method[] allMethods = clazz.getMethods(); // 找到所有方法 int parameterLen = parameterTypes.length; // 实参个数 List<Method> similarMethodList = new ArrayList<Method>(); // 所有能够执行parameterTypes实参的方法 for (Method method : allMethods) { if (!method.getName().equals(methodName)) { continue; } if (method.getParameterTypes().length == parameterLen) { boolean isSimilarType = true; Class<?>[] formaParameterTypes = method.getParameterTypes(); // 得到形参 for (int i = 0; i < parameterLen; i++) { if (!isSameOrSupperType(formaParameterTypes[i], parameterTypes[i])) { isSimilarType = false; break; } } if (isSimilarType) { similarMethodList.add(method); } }// if } if (similarMethodList.isEmpty()) { StringBuilder sb = new StringBuilder(); sb.append(methodName); sb.append("("); if (null != parameterTypes) { StringBuilder parameterTypeBuilder = new StringBuilder(); for (Class<?> parameterType : parameterTypes) { parameterTypeBuilder.append(parameterType.getName()) .append(","); } if (parameterTypeBuilder .charAt(parameterTypeBuilder.length() - 1) == ',') { parameterTypeBuilder = parameterTypeBuilder .deleteCharAt(parameterTypeBuilder.length() - 1); } sb.append(parameterTypeBuilder); } sb.append(")"); throw new NoSuchMethodException("没有这样的方法. " + sb.toString()); } List<Integer> parameterMatchingCountList = new ArrayList<Integer>(); // 存放各个能够执行parameterTypes形参的方法对形参的严格匹配个数 for (Method method : similarMethodList) { int parameterMatchingCount = 0; Class<?>[] formaParameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterLen; i++) { if (formaParameterTypes[i].getName().equals( parameterTypes[i].getName())) { parameterMatchingCount++; } } parameterMatchingCountList.add(parameterMatchingCount); } int maxMatchingCountIndex = 0; for (int i = 1; i < parameterMatchingCountList.size(); i++) { if (parameterMatchingCountList.get(maxMatchingCountIndex) .intValue() < parameterMatchingCountList.get(i).intValue()) { maxMatchingCountIndex = i; } } return similarMethodList.get(maxMatchingCountIndex); // } public static boolean isSameOrSupperType(Class<?> clsA, Class<?> clsB) { if (!clsA.isPrimitive() && !clsB.isPrimitive()) { return clsA.isAssignableFrom(clsB); } return isSameBasicType(clsA, clsB); } public static boolean isSameBasicType(Class<?> clsA, Class<?> clsB) { if (isIntType(clsA) && isIntType(clsB)) { return true; } if (isLongType(clsA) && isLongType(clsB)) { return true; } if (isBooleanType(clsA) && isBooleanType(clsB)) { return true; } if (isByteType(clsA) && isByteType(clsB)) { return true; } if (isCharType(clsA) && isCharType(clsB)) { return true; } if (isFloatType(clsA) && isFloatType(clsB)) { return true; } if (isDoubleType(clsA) && isDoubleType(clsB)) { return true; } if (isShortType(clsA) && isShortType(clsB)) { return true; } return false; } public static boolean isCharType(Class<?> clazz) { return Character.class.isAssignableFrom(clazz) || Character.TYPE.isAssignableFrom(clazz); } public static boolean isBooleanType(Class<?> clazz) { return Boolean.class.isAssignableFrom(clazz) || Boolean.TYPE.isAssignableFrom(clazz); } public static boolean isIntType(Class<?> clazz) { return Integer.class.isAssignableFrom(clazz) || Integer.TYPE.isAssignableFrom(clazz); } public static boolean isLongType(Class<?> clazz) { return Long.class.isAssignableFrom(clazz) || Long.TYPE.isAssignableFrom(clazz); } public static boolean isFloatType(Class<?> clazz) { return Float.class.isAssignableFrom(clazz) || Float.TYPE.isAssignableFrom(clazz); } public static boolean isDoubleType(Class<?> clazz) { return Double.class.isAssignableFrom(clazz) || Double.TYPE.isAssignableFrom(clazz); } public static boolean isByteType(Class<?> clazz) { return Byte.class.isAssignableFrom(clazz) || Byte.TYPE.isAssignableFrom(clazz); } public static boolean isShortType(Class<?> clazz) { return Short.class.isAssignableFrom(clazz) || Short.TYPE.isAssignableFrom(clazz); } }服务端:
package rmiproj.newserver; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; /** * 使用了统一的暴露接口以后,服务端就不用在修修改改了,永远都是暴露CommonRmiServiceImpl,不管新增什么方法都是一样滴调用这个接口来执行 * 其实,spring中的rmi道理也是一样滴 * */ public class Server { public static void main(String[] args) { try { LocateRegistry.createRegistry(1099); Naming.rebind("service", new CommonRmiServiceImpl()); System.out.println(LocateRegistry.getRegistry()); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }用来测试的客户端:
package rmiproj.newclient; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class Test { public static void main(String[] args) { try { rmiproj.newserver.CommonRmiService my = (rmiproj.newserver.CommonRmiService) Naming .lookup("rmi://localhost:1099/" + "service"); System.out.println(my.execute("rmiproj.newserver.MyCalculaterImpl", "plus", new Object[] { 1L, 2L })); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } } }
测试结果:
服务器端启动:
客户端测试结果:
服务器端显示的内容:
这个例子其实也没有什么,就是把暴露的方法改为了一个通过反射来执行服务器端的其他方法的方法了,在这种方式下,服务器端书写代码会变的简单,不用在管什么Remote和UnicastRemoteObject了,按照平时的接口----》实现类书写就可以了,同时服务器端的server代码也不用变了。但是问题是客户端要知道要调用的类的全名,方法的名称以及参数的相关信息。
四、使用sll的双向认证的rmi(个人感觉很高级了··信息是安全的,访问也是安全的)
在例子二里面初步的考虑了安全问题,但是我个人不喜欢,也不熟悉那个policy文件的配置,而且感觉和很多框架要使用的securityManager会有冲突,而且也有更好的更安全的办法。就是这里介绍给大家了。
package rmiproj.ssl_1; import java.rmi.Remote; import java.rmi.RemoteException; /*本人比较懒惰了··这里就写个hello吧,还是那一套继承Remote,方法中需要抛出RemoteException*/ public interface Hello extends Remote { public String sayHello() throws RemoteException; }
package rmiproj.ssl_1; import java.io.FileInputStream; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.security.KeyStore; import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; /** * 这个是hello的实现类,在构造方法和getCntext那里有些幺蛾子,需要细细看看,其他没什么了 * * */ public class HelloImpl_1 extends UnicastRemoteObject implements Hello { protected HelloImpl_1() throws IllegalArgumentException, Exception { /** * 这个构造方法需要细细的说道一下了 * 先说super * * 第一个参数指明了这个remoteObject接受请求的端口,我就设置成了0; * * 第二个参数设置客户端要使用的套接字工厂,我设置了自己实现的MySslRMIClientSocketFactory,这个具体是什么,后面介绍 * * 第三个参数设置了服务端要是用的套接字工场,我是用了SslRMIServerSocketFactory;其中getContext在后面说, * new String[] { "SSL_RSA_WITH_RC4_128_MD5" }是加密算法,new String[] { "TLSv1" }使用的协议。 * * 第四个参数是是否开启对客户端的验证,要做双向验证必然是true了 * * */ super(0, new MySslRMIClientSocketFactory(), new SslRMIServerSocketFactory(getContext(), new String[] { "SSL_RSA_WITH_RC4_128_MD5" }, new String[] { "TLSv1" }, true)); } /** * */ private static final long serialVersionUID = 1L; @Override public String sayHello() throws RemoteException { //在这里我没什么好说的·· return "Hello World"; } public static SSLContext getContext() throws Exception { String key = "d:/keys/m.jks";//服务器端的秘钥 String trust = "d:keys/trustclient.jks";//服务器端信任的证书 KeyStore keyStore = KeyStore.getInstance("JKS");//使用JKS的keyStore /*加载服务器的秘钥开始*/ keyStore.load(new FileInputStream(key), "123456".toCharArray()); KeyStore trustStore = KeyStore.getInstance("JKS"); /*加载服务器的秘钥结束*/ /*加载服务器信任的证书开始*/ trustStore.load(new FileInputStream(trust), "123456".toCharArray()); /*加载服务器信任的证书结束*/ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm());//创建了秘钥管理工厂 kmf.init(keyStore, "mipengcheng".toCharArray());//用服务器的秘钥来初始化秘钥工厂 TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm());//建立了信任证书工厂 tmf.init(trustStore);//用服务器信任的证书来初始化 SSLContext sslc = SSLContext.getInstance("TLSv1");//获得context实例 sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());//初始化context return sslc; } /** * 在这里我不使用Naming了,而是直接使用registry来绑定,其实功能是一样的,但是这个方式更适合不指定url的访问方式 * */ public static void main(String[] args) throws Exception { //获得注册的registry,因为我注册的registry也使用了sll,所以这里也要在参数中加入new MySslRMIClientSocketFactory() Registry registry = LocateRegistry.getRegistry(null, 3000, new MySslRMIClientSocketFactory()); HelloImpl_1 helloImpl = new HelloImpl_1(); //绑定 registry.bind("HelloServer", helloImpl); System.out.println("HelloServer bound in registry"); } }
package rmiproj.ssl_1; import java.io.FileInputStream; import java.rmi.registry.LocateRegistry; import java.security.KeyStore; import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; public class RmiRegistry { public static void main(String[] args) throws Exception { String key = "d:/keys/m.jks"; String trust = "d:keys/trustclient.jks"; KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(key), "123456".toCharArray()); KeyStore trustStore = KeyStore.getInstance("JKS"); trustStore.load(new FileInputStream(trust), "123456".toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(keyStore, "mipengcheng".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); SSLContext sslc = SSLContext.getInstance("SSLv3"); sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); /*上面的一套代码就是生成一个指定了秘钥和信任证书的context,就不多说了*/ /*下面我们在300端口创建一个registry,并且指定了客户端套接字和服务端套接字,和HelloImpl_1中的很像*/ LocateRegistry.createRegistry(3000, new MySslRMIClientSocketFactory(), new SslRMIServerSocketFactory(sslc, new String[] { "SSL_RSA_WITH_RC4_128_MD5" }, new String[] { "TLSv1" }, true)); System.out.println("RMI Registry running on port 3000"); Object object = new Object(); synchronized (object) { object.wait(); } } }
package rmiproj.ssl_1; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; import java.security.KeyStore; import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import javax.rmi.ssl.SslRMIClientSocketFactory; public class MySslRMIClientSocketFactory extends SslRMIClientSocketFactory { /** * * 这个类继承了SslRMIClientSocketFactory,我也是看文档的, * 那个什么什么说SslRMIClientSocketFactory为了避免各种各种的问题 * 都是使用默认的jre中配置的证书来创建socket,要是你自己的客户端能验证服务器 * ,服务器验证你的客户端,而且是使用自己的证书,就重写一下createSocket这个方法。。。云云 那就重写吧 */ private static final long serialVersionUID = 1L; public MySslRMIClientSocketFactory() { super(); } @Override public Socket createSocket(String host, int port) throws IOException { String key = "d:/keys/trustm.jks"; String client = "d:/keys/client.jks"; try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream(key), "123456".toCharArray()); KeyStore clientStore = KeyStore.getInstance(KeyStore .getDefaultType()); clientStore.load(new FileInputStream(client), "123456".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); KeyManagerFactory kmf = KeyManagerFactory .getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(clientStore, "123456".toCharArray()); SSLContext sslc = SSLContext.getInstance("TLSv1"); sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); /*上面的没什么好说的了。。。就是要说说,证书换成了客户端要信任的证书和客户端的秘钥*/ SSLSocketFactory sslSocketFactory = sslc.getSocketFactory();//通过sslc获得socketFactory SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port);//建立socket return socket; } catch (Exception e) { e.printStackTrace(); return null; } } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object obj) { return super.equals(obj); } }
package rmiproj.ssl_1; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; /* * 测试的客户端 * */ public class HelloClient { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("localhost", 3000, new MySslRMIClientSocketFactory());/* * 这里要特别注意, * 因为我在registry的服务端配置了要进行双向认证 * , * 所以这里的registry在get的时候一定要添加参数MySslRMIClientSocketFactory */ Hello hello = (Hello) registry.lookup("HelloServer"); System.out.println(hello.sayHello()); } }
使用到的证书
测试结果
服务器端启动:
先运行RmiRegistry
再运行HelloImpl_1
运行客户端进行测试
这样在ssl上的RMI就配置完成了,测试也是通过的。错误的测试我就不做了。可以尝试客户端不传自己的证书,链接是否成功。也可以尝试服务器端不传证书,服务器端不信任客户端,客户端不信任服务器端等等的情况,我都测试过,都是无法建立链接的。
同时那几个证书的生成过程我也忽略了没有说,网上指导这些证书生成的博文很多,我就不细说了。
五、在spring中使用RMI。
spring最最重要的就是配置文件了,那么就直接看配置文件了
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-lazy-init="false"> <!-- 这个serviceExporter就是spring用来暴露RMI服务的bean --> <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter"> <property name="serviceName" value="AccountService" /><!-- 配置暴露的服务名 --> <property name="service" ref="accountService" /><!-- 暴露的类 --> <property name="serviceInterface" value="com.mpc.test.inter.AccountService" /><!--暴露类的接口 --> <property name="registryPort" value="8080" /><!-- registry用的端口 --> <property name="servicePort" value="8088" /><!-- remoteObject通信用的接口 --> <property name="clientSocketFactory" ref="mySslRMIClientSocketFactory"></property><!-- 客户端用的socketFactory --> <property name="serverSocketFactory"><!-- 服务端用的socketfactory --> <!-- 偷懒一下··直接使用在类com.mpc.test.ContextUtil中 的getContext来获得这个属性了 --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetClass" value="com.mpc.test.ContextUtil" /> <property name="targetMethod" value="getContext" /> </bean> </property> <!-- 配置拦截器,用来对这些方法做一些aop上的操作,我这里就做了ip的限制。 要注意的是,如果你暴露的服务没有实现Remote接口,那么spring会用他的代理来操作你的暴露的服务,这种情况下这个配置再回生效。当然一般是不会有人放着简单的不用,自己做复杂的东西的 --> <property name="interceptors"> <list> <ref bean="securityInterceptor" /> </list> </property> </bean> <!-- 客户端的socketFactory --> <bean id="mySslRMIClientSocketFactory" class="com.mpc.test.MySslRMIClientSocketFactory"></bean> <!-- 用来处理ip的拦截器 --> <bean id="securityInterceptor" class="com.mpc.test.SecurityInterceptor"> <property name="allowed"> <set> <value>192.168.1.210</value> </set> </property> </bean> <!-- 要暴露的服务 --> <bean id="accountService" class="com.mpc.test.clazz.AccountServiceImpl" /> </beans>
package com.mpc.test; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; import java.security.KeyStore; import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import javax.rmi.ssl.SslRMIClientSocketFactory; /*在例四中已经使用过了,我就不多说了*/ public class MySslRMIClientSocketFactory extends SslRMIClientSocketFactory { /** * */ private static final long serialVersionUID = 1L; public MySslRMIClientSocketFactory() { super(); } @Override public Socket createSocket(String host, int port) throws IOException { String key = "d:/keys/trustm.jks"; String client = "d:/keys/client.jks"; try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream(key), "123456".toCharArray()); KeyStore clientStore = KeyStore.getInstance(KeyStore .getDefaultType()); clientStore.load(new FileInputStream(client), "123456".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); KeyManagerFactory kmf = KeyManagerFactory .getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(clientStore, "123456".toCharArray()); SSLContext sslc = SSLContext.getInstance("TLSv1"); sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); SSLSocketFactory sslSocketFactory = sslc.getSocketFactory(); SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port); return socket; } catch (Exception e) { e.printStackTrace(); return null; } } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object obj) { return super.equals(obj); } }
package com.mpc.test; import java.io.FileInputStream; import java.security.KeyStore; import java.security.SecureRandom; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; /*在例四中也是使用过的*/ public class ContextUtil { public static SslRMIServerSocketFactory getContext() throws Exception { String key = "d:/keys/m.jks"; String trust = "d:keys/trustclient.jks"; KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(key), "123456".toCharArray()); KeyStore trustStore = KeyStore.getInstance("JKS"); trustStore.load(new FileInputStream(trust), "123456".toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(keyStore, "mipengcheng".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); SSLContext sslc = SSLContext.getInstance("SSLv3"); sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); return new SslRMIServerSocketFactory(sslc, new String[] { "SSL_RSA_WITH_RC4_128_MD5" }, new String[] { "TLSv1" }, true); } }
package com.mpc.test; import java.rmi.server.RemoteServer; import java.util.Set; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class SecurityInterceptor implements MethodInterceptor { private Set<String> allowed; /** 定义的拦截器,用来处理ip的问题 */ public Object invoke(MethodInvocation methodInvocation) throws Throwable { String cilentHost = RemoteServer.getClientHost(); if (allowed != null && allowed.contains(cilentHost)) { return methodInvocation.proceed(); } else { throw new SecurityException("非法访问。"); } } public void setAllowed(Set<String> allowed) { this.allowed = allowed; } }
package com.mpc.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; /** * Unit test for simple App. * 测试··服务器端 */ public class AppTest extends TestCase { public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext( "application-context.xml"); Object lock = new Object(); synchronized (lock) { lock.wait(); } } /** * Create the test case * * @param testName * name of the test case * * * */ public AppTest(String testName) throws Exception { } /** * @return the suite of tests being tested */ public static Test suite() { return new TestSuite(AppTest.class); } /** * Rigourous Test :-) */ public void testApp() { assertTrue(true); } }
客户端的xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-lazy-init="false"> <!-- 这个就是spring用来调用远程方法的bean没什么好说的--> <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <!-- 请求的地址 --> <property name="serviceUrl" value="rmi://192.168.1.210:8080/AccountService" /> <!-- 绑定的接口 --> <property name="serviceInterface" value="com.mpc.test.inter.AccountService" /> </bean> </beans>
package com.mpc.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.mpc.test.inter.AccountService; /*测试类*/ public class TestClient { public static void main(String[] args) throws Exception { int i = 1; while (i < 2048) { i <<= 1; new MyThread().start(); Thread.sleep(10000); } } static class MyThread extends Thread { @Override public void run() { ApplicationContext ctx = new ClassPathXmlApplicationContext( "client.xml"); AccountService accountService = (AccountService) ctx .getBean("accountService"); String result = accountService.shoopingPayment( String.valueOf(System.currentTimeMillis()), (byte) 5); System.out.println(result); } } }
测试结果:
服务器端启动:
客户端测试
服务端的显示:
测试是成功的,这里我只配置了clientSocketFactory和serverSocketFactory,在spring中,同样可以为registry来配置端口的工厂,这两个属性分别是:
private RMIClientSocketFactory registryClientSocketFactory;
private RMIServerSocketFactory registryServerSocketFactory;
这里我没有配置,因为我感觉有一个就足够安全了,有兴趣的话,可以自己配置一下。
还有就是spring中的具体实现原理,大家有性趣可以看看源代码,我看了看也就是代理代理在代理····但是写的很好,篇幅所限就不做介绍了,网上也有人做这方面的介绍的。我提供一个网址大家可以看看:spring的rmi的类的内容
个人能力有限,如果有什么不对之处,还希望能指正批评。