1 背景
正在经手的项目的web应用之间是通过spring的controller方式暴露接口,然后使用httpClient进行访问。普普通通的增删改查功能也得写上七八个方法才能实现,实在是写到心累。于是乎想要增加一种远程调用方式,本着尽量遵循原有安全验证策略的原则,对httpinvoker做了些小的调整。不过方案最终被负责人否了,只能继续写可爱的httpClient方法。只好抹去业务逻辑,把代码变成博客安安静静的躺在知识库里。
2 思路与实现
通过对源代码的解读,发现spring和httpinvoker在进行远程调用时,主要是通过RemoteInvocation这个类来传递参数。于是调整的思路就是借助这个类传递我们的认证信息,在服务端读取的同时进行安全认证。以达到最终的目的。
首先是自定义安全认证信息类,这个类在客户端负责存放安全认证信息和生成安全密钥,在服务端负责解密:
/**
* 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类以便添加我们的验证的信息。
HttpInvokerProxyFactoryBean类的重写:
/**
* 详情参见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文件,设置远程代理类:
<?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:
/**
* 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 小结
因为改动并不复杂,在代码里也写了些简单的注释,就不对源码做多余的分析了,有缺陷或者值得改进的地方欢迎指出...
来源:oschina
链接:https://my.oschina.net/u/1268144/blog/422303