1 背景 java
正在经手的项目的web应用之间是经过spring的controller方式暴露接口,而后使用httpClient进行访问。普普统统的增删改查功能也得写上七八个方法才能实现,实在是写到心累。因而乎想要增长一种远程调用方式,本着尽可能遵循原有安全验证策略的原则,对httpinvoker作了些小的调整。不过方案最终被负责人否了,只能继续写可爱的httpClient方法。只好抹去业务逻辑,把代码变成博客安安静静的躺在知识库里。 web
2 思路与实现 spring
经过对源代码的解读,发现spring和httpinvoker在进行远程调用时,主要是经过RemoteInvocation这个类来传递参数。因而调整的思路就是借助这个类传递咱们的认证信息,在服务端读取的同时进行安全认证。以达到最终的目的。 安全
首先是自定义安全认证信息类,这个类在客户端负责存放安全认证信息和生成安全密钥,在服务端负责解密: ide
/** * HttpInvoker调用验证信息类 */ public class MyHttpInvokerAuthInfo { private static final Logger LOGGER = LoggerFactory.getLogger(MyHttpInvokerAuthInfo.class); //用户名KEY public static final String USERNAME_KEY = "USERNAME_KEY"; //密码KEY public static final String PASSWORD_KEY = "PASSWORD_KEY"; //随机生成的KEY1 public static final String FIRST_KEY = "FIRST_KEY"; //随机生成的KEY2 public static final String SECOND_KEY = "SECOND_KEY"; private String username; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } /** * 获取加密信息MAP */ public Map<String, Serializable> getSecurityMap(){ if(StringUtils.isBlank(password)){ return null; } Map<String, Serializable> securityMap = new HashMap<String, Serializable>(); //TODO 添加本身的安全加密逻辑,并把须要认证的数据放入securityMap中 return securityMap; } /** * 生成密钥 */ public static String getSecurityKey(String firstKey, String secondKey, String thirdKey) { String security = null; //TODO 生成本身的密钥 return security; } /** * 对认证信息进行校验 */ public static boolean validatePassword(String key, Map<String, Serializable> keyMap) { boolean result = false; try { //TODO 校验逻辑 } catch (Exception e) { LOGGER.error("密钥校验失败", e); } return result; } }
而后是客户端,客户端须要重写spring的HttpInvokerProxyFactoryBean类和HttpInvokerClientInterceptor类以便添加咱们的验证的信息。 ui
HttpInvokerProxyFactoryBean类的重写: this
/** * 详情参见Spring的HttpInvokerProxyFactoryBean类,本类与其彻底一致 */ public class MHttpInvokerProxyFactoryBean extends MyHttpInvokerClientInterceptor implements FactoryBean<Object> { private Object serviceProxy; @Override public void afterPropertiesSet() { super.afterPropertiesSet(); if (getServiceInterface() == null) { throw new IllegalArgumentException("Property 'serviceInterface' is required"); } this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader()); } public Object getObject() { return this.serviceProxy; } public Class<?> getObjectType() { return getServiceInterface(); } public boolean isSingleton() { return true; } }
HttpInvokerClientInterceptor类的重写: 加密
/** * 对Spring的HttpInvokerClientInterceptor类的重写 * 添加 MyHttpInvokerAuthInfo 这一验证信息参数 * 重写invoke()方法.... * 重写getHttpInvokerRequestExecutor()方法,修改默认HttpInvokerRequestExecutor为CommonHttpInvokerRequestExecutor * 其他保持一致 */ public class MyHttpInvokerClientInterceptor extends RemoteInvocationBasedAccessor implements MethodInterceptor, HttpInvokerClientConfiguration { private String codebaseUrl; private HttpInvokerRequestExecutor httpInvokerRequestExecutor; /** * 验证参数,存放服务端须要的认证信息,并用认证信息生成密钥 */ private MyHttpInvokerAuthInfo myHttpInvokerAuthInfo; public void setCodebaseUrl(String codebaseUrl) { this.codebaseUrl = codebaseUrl; } public String getCodebaseUrl() { return this.codebaseUrl; } public void setHttpInvokerRequestExecutor(HttpInvokerRequestExecutor httpInvokerRequestExecutor) { this.httpInvokerRequestExecutor = httpInvokerRequestExecutor; } /** * 返回一个HTTP请求执行器 * 将默认执行器修改成CommonsHttpInvokerRequestExecutor */ public HttpInvokerRequestExecutor getHttpInvokerRequestExecutor() { if (this.httpInvokerRequestExecutor == null) { CommonsHttpInvokerRequestExecutor executor = new CommonsHttpInvokerRequestExecutor(); executor.setBeanClassLoader(getBeanClassLoader()); this.httpInvokerRequestExecutor = executor; } return this.httpInvokerRequestExecutor; } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); // Eagerly initialize the default HttpInvokerRequestExecutor, if needed. getHttpInvokerRequestExecutor(); } /** * 重写调用方法,向RemoteInvocation中添加项目须要的验证信息 */ public Object invoke(MethodInvocation methodInvocation) throws Throwable { if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]"; } RemoteInvocation invocation = createRemoteInvocation(methodInvocation); try { //生成并写入验证信息 if(myHttpInvokerAuthInfo != null){ if(invocation.getAttributes() == null){ invocation.setAttributes(myHttpInvokerAuthInfo.getSecurityMap()); }else{ invocation.getAttributes().putAll(myHttpInvokerAuthInfo.getSecurityMap()); } } }catch (Exception e){ logger.error("设置验证参数发生异常,请求将可能被服务端拦截...", e); } RemoteInvocationResult result = null; try { result = executeRequest(invocation, methodInvocation); } catch (Throwable ex) { throw convertHttpInvokerAccessException(ex); } try { return recreateRemoteInvocationResult(result); } catch (Throwable ex) { if (result.hasInvocationTargetException()) { throw ex; } else { throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() + "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex); } } } protected RemoteInvocationResult executeRequest( RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception { return executeRequest(invocation); } protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception { return getHttpInvokerRequestExecutor().executeRequest(this, invocation); } protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) { if (ex instanceof ConnectException) { throw new RemoteConnectFailureException( "Could not connect to HTTP invoker remote service at [" + getServiceUrl() + "]", ex); } else if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError || ex instanceof InvalidClassException) { throw new RemoteAccessException( "Could not deserialize result from HTTP invoker remote service [" + getServiceUrl() + "]", ex); } else { throw new RemoteAccessException( "Could not access HTTP invoker remote service at [" + getServiceUrl() + "]", ex); } } public MyHttpInvokerAuthInfo getMyHttpInvokerAuthInfo() { return myHttpInvokerAuthInfo; } public void setMyHttpInvokerAuthInfo(MyHttpInvokerAuthInfo myHttpInvokerAuthInfo) { this.myHttpInvokerAuthInfo = myHttpInvokerAuthInfo; } }
而后须要配置xml文件,设置远程代理类: url
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 安全认证类 --> <bean id="myAuthInfo" class="cn.com.test.httpinvoker.MyHttpInvokerAuthInfo"> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean> <!-- 接口申明开始 --> <bean id="myTestService" class="cn.com.test.httpinvoker.MyHttpInvokerProxyFactoryBean"> <property name="myHttpInvokerAuthInfo" ref="myAuthInfo"/> <property name="serviceUrl" value="${url}/inner/myTest.service"/> <property name="serviceInterface" value="cn.com.test.service.MyTestService"/> </bean> </beans>
最后是服务端,服务端须要经过httpinvoker来暴露本身的接口,因此须要重写接口暴露类HttpInvokerServiceExporter: spa
/** * HttpInvoker接口暴露类,添加验证支持 * 继承自spring的HttpInvokerServiceExporter类,重写其handleRequest()方法 */ public class MyHttpInvokerServiceExporter extends HttpInvokerServiceExporter { private static final Logger LOGGER = LoggerFactory.getLogger(MyHttpInvokerServiceExporter.class); /** * 重写处理方法,添加安全认证 */ @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { RemoteInvocation invocation = readRemoteInvocation(request); if(!isSecurityRequest(invocation)){ String message = "Security Forbidden,this is not security request"; try { response.getWriter().println(message); } catch (IOException ex) { LOGGER.error(ex.getMessage(),ex); } return; } RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); writeRemoteInvocationResult(request, response, result); } catch (ClassNotFoundException e) { throw new NestedServletException("Class not found during deserialization", e); } } /** * 安全认证方法 */ protected boolean isSecurityRequest(RemoteInvocation invocation){ try { String username = invocation.getAttribute(MyHttpInvokerAuthInfo.USERNAME_KEY).toString(); return MyHttpInvokerAuthInfo.validatePassword(username, invocation.getAttributes()); } catch (Exception e) { LOGGER.error("读取验证信息失败...", e.getMessage()); } return false; } }
最后在发布接口时,把接口暴露类指向重写后的MyHttpInvokerServiceExporter:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="/inner/myTest.service" class="cn.com.test.httpinvoker.MyHttpInvokerServiceExporter"> <!-- myTestService 经过注释来申明 --> <property name="service" ref="myTestService"/> <property name="serviceInterface" value="cn.com.test.service.MyTestService"/> </bean> </beans>
3 小结
由于改动并不复杂,在代码里也写了些简单的注释,就不对源码作多余的分析了,有缺陷或者值得改进的地方欢迎指出...