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 小結
由於改動並不複雜,在代碼裏也寫了些簡單的註釋,就不對源碼作多餘的分析了,有缺陷或者值得改進的地方歡迎指出...