我们经常会使用hessian接口,但是hessian接口默认的情况下是不会进行加密的,原来在项目中使用了MD5的加密,这次希望通过DSA 加密解决 C- S的安全通讯,一方面确定客户端是有权限调用的(给出了publicKey),另一方面保证传输的数据安全
基本思路就是采用 新的HessianProxyFactory和 HessianServiceExporter 代替原来的发送和接收端,发送请求时进行私钥加密,接收时进行公钥的解密。
基本逻辑实现如下:
/** * 实现带加密功能的hessian client * * <pre> * 结合spring hessian使用,使用DSAHessianProxyFactory替换默认的HessianProxyFactory * <bean id="heessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"> * <property name="proxyFactory"> * <bean class="com.alibaba.pivot.common.hessian.DSAHessianProxyFactory"> * <property name="secureKey" value="xxxxx"/> * </bean> * </property> * </bean> * </pre> * * @author job 2010-01-28 上午10:20:01 */ public class DSAHessianProxyFactory extends HessianProxyFactory { private static final Logger log = LoggerFactory.getLogger(DSAHessianProxyFactory.class); private DSAService dsaService; private String keyPairName; /** * 固定的加密字符串 */ private String secureKey; protected URLConnection openConnection(URL url) throws IOException { String signature = null; String baseUrl = url.toString(); StringBuilder queryBuilder = new StringBuilder(); queryBuilder.append(baseUrl); // 生成时间戳 long timestamp = System.currentTimeMillis(); // 生成方法签名 try { signature = dsaService.sign(secureKey + "|" + Long.toString(timestamp), keyPairName); } catch (NoSuchKeyPairException ne) { log.error("error in DSAHessianProxyFactory.openConnection,no such key" + keyPairName, ne); } catch (DSAException de) { log.error("error in DSAHessianProxyFactory.openConnection,DSA sign error" + keyPairName, de); } if (!"?".equals(queryBuilder.charAt(queryBuilder.length() - 1))) { queryBuilder.append("?"); } queryBuilder.append("sign="); if (signature != null) { queryBuilder.append(StringEscapeUtil.escapeURL(signature)); } queryBuilder.append("&time="); queryBuilder.append(timestamp); URL secureUrl = new URL(queryBuilder.toString()); URLConnection conn = secureUrl.openConnection(); conn.setDoOutput(true); if (getReadTimeout() > 0) { try { conn.setReadTimeout((int) getReadTimeout()); } catch (Throwable e) { } } // 设置自定义的content-type conn.setRequestProperty("Content-Type", "application/octet-stream"); return conn; }
输出时的加密:
/** * 处理加密的hessian server, 代码来自pivot-p4p * * <pre> * 1. timeout 设置超时时间,单位秒. (客户端会在每次请求中添加时间戳,如果设置了该值,就会进行对客户端请求时间是否超时进行验证) * 2. secureKey 服务端密钥串 (如果设置了该值,就会进行对客户端请求加密串进行验证) * 3. allowedClients 客户端IP验证 (如果设置了该值,就会进行对客户端请求IP进行验证) * </pre> * * @author job 2010-01-28 上午09:53:02 */ public class DSAHessianServiceExporter extends HessianServiceExporter { private static Logger log = LoggerFactory.getLogger(DSAHessianServiceExporter.class); /** 请求过期时间 **/ private long timeout; /** 令牌原文 **/ private String secureKey; /** 允许访问的客户端 **/ private String allowedClients; /** dsa 加密服务 **/ private DSAService dsaService; /** 密钥名称 **/ private String keyPairName; public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1.如果设置了允许的IP地址,需要进行客户端IP地址验证 if (StringUtil.isNotBlank(allowedClients)) { String clientIP = getClientIPAddress(request); if (!isAllowedClient(clientIP)) { log.error("hessian authentication error:" + clientIP + " not in allowedList " + allowedClients); return; } } // 2.如果设置了令牌或者过期时间,需要判断时间格式是否合法 String strTimestamp = request.getParameter("time"); long timestamp = 0; if (timeout > 0 || StringUtil.isNotBlank(secureKey)) { if (strTimestamp == null) { log.error("hessian authentication error:timestamp not exist!"); return; } try { timestamp = Long.parseLong(strTimestamp); } catch (NumberFormatException e) { log.error("hessian authentication error:timestamp is not number!"); return; } } // 3.如果设置了过期时间,需要判断请求是否过期 if (timeout > 0) { // 判断请求是否过期 if (isRequestExpired(timestamp)) { log.error("hessian authentication error:request is expired!"); return; } } // 4. 如果设置了令牌,需要进行签名校验 if (StringUtil.isNotBlank(secureKey)) { // 4.1 得到签名 String signature = request.getParameter("sign"); if (signature == null) { log.error("hessian authentication error:signatures not exist!"); return; } try { // 4.2 验证签名是否一致 boolean isRight = dsaService.check(secureKey + "|" + timestamp, signature, keyPairName); if (!isRight) { log.error("hessian authentication error:signatures not match!"); return; } } catch (NoSuchKeyPairException ne) { log.error("error in DSAHessianServiceExporter.handleRequest,no such key" + keyPairName, ne); } catch (DSAException de) { log.error("error in DSAHessianServiceExporter.handleRequest,DSA sign error", de); } } // 5. 设置hessian context-type response.setContentType("application/octet-stream"); super.handleRequest(request, response); }
对于key的产生可以用java自带的工具 keystore产生。