我们对于消息服务这块的整体设计是客户端连接服务端后,发起身份认证,携带应用编码和密钥,并对密钥进行了hash加密,服务端认证通过后,建立长连接。
对于长连接,我们认为是安全,恶意用户无法直接往长连接通道中发送一条伪造的消息,因此没有必要对每条消息进行签名和验签,从而提升了性能。但这里还有一个小漏洞,未认证的应用是防住了,已认证的应用如果进行一些越权操作如何应对呢?
例如,物流系统LMS对接了两个承运商的TMS系统,如果A承运商系统,发送了一条消息确认,但消息中携带的应用编码是B的,实际发生这种情况的可能性不大,但是还是有一定隐患的。。
该问题的关键出在,我们识别应用,使用的消息中携带的应用编码,而不是登录认证成功后连接中保存的应用编码。
有两种方案,一是在使用应用编码的地方,从连接通道中获取登录成功时保存的应用标识值,二是在数据验证环节,验证下客户端消息携带的应用编码与通道中的应用编码是否一致就行了。
综合考虑下,方案1从通道中获取应用编码,在很多使用应用编码的方法中,需要额外获取到通道这个对象才行,比较麻烦;方案2比较便利,只做一点小变动就行了,因此我们采用方案2.
相关改造如下:
因为只要服务端需要验证应用编码与消息通道是否一致,因此不能放到客户端和服务端公用的基本数据验证处理中,而是放到了服务端的MessageHandler中,在应用验证环节,比对消息中携带的应用编码与通道对应的应用编码是否一致就行了,如果不一致,则抛出异常。同时记录一条错误日志,可事后追溯是否存在对接系统尝试使用他人的应用标识发送消息的情况。
/**
* 验证应用
*
* @param publishAppCode 消息应用编码
* @param appCode 通道应用编码
*/
protected void validateAppCode(String publishAppCode,String appCode) {
//这地方需要对appCode判空,如登录操作,通道的appCode还不存在
if(appCode!=null && !appCode.equals(publishAppCode)){
log.error("应用标识与消息通道不一致,应用标识:{},消息通道{}",publishAppCode,appCode);
throw new MessageException("S201", "应用标识与消息通道不一致");
}
try {
ApiApp app =apiAppService.getByCode(publishAppCode);
if(app.getStatus().equals(StatusEnum.DEAD.name())){
throw new MessageException("S203", "应用被停用");
}
}catch (Exception ex){
throw new MessageException("S202", "应用标识无效");
}
}
通道的应用标识如何获取呢?我们改造下全局容器,新增一个有channel对象获取appCode的方法
/**
* 根据应用编码获取相应通道
* @param channel
*/
public static String getAppCode(Channel channel) {
//从channel属性中读取到用户标识
AttributeKey<String> appCodeAttribute=AttributeKey.valueOf(APP_CODE_KEY);
return channel.attr(appCodeAttribute).get();
}
然后在请求/响应消息处理器中,调用全局容器的方法,获取到appCode,传入到框架验证环节
/**
* 消息处理
*
* @param message 消息
* @param channel 通道
*/
public void handleMessage(RequestMessage requestMessage, Channel channel) {
// 记录消息请求日志
apiMessageLogService.createRequestPart(requestMessage);
//验证框架
String appCode = MessageServerHolder.getAppCode(channel);
validateFramework(requestMessage,appCode);
//将请求消息状态默认设置为无需发送
apiMessageLogService.updateStatus(MessageStatusEnum.NOT_TO_REQUEST.name(),requestMessage.getId());
//特殊处理
messageOperation(requestMessage, channel);
//发送响应至消息发送者
sendResponse(requestMessage, channel);
//消息处理(复制及转发)
if(isNeedRepost()){
repostMessage(requestMessage.getTopic(),requestMessage.getContent());
}
}
改造完成,进行测试,故意造一条不一致的数据,服务端会打印错误日志:
应用标识与消息通道不一致,应用标识:ABC,消息通道SCS
同时响应客户端一条错误消息
{"errorCode":"500","errorMessage":"应用标识与消息通道不一致",
"id":"1489404728953479170","messageType":"RESPONSE","publishAppCode":"MessageServer",
"publishTime":"2022-02-04 09:05:33","requestMessageId":"1489404726461988866","result":"ERROR",
"topic":"framework.error.response"}