回调鉴权
更新时间: 2022/08/18 09:26:59
功能介绍
-
1、网易云信服务器可以通过消息抄送功能,将消息数据和事件通知等实时同步(云信发出HTTP-POST请求,用JSON格式的body来承载实际的内容)给开发者预设的服务器。请前往网易云信控制台 > 通信与视频(云信) > 选择对应的应用 > 消息抄送配置 > 设置接收抄送的服务器地址。抄送地址支持http和https,如果是https地址,开发者需要保证https证书的有效性,且配置时需携带http://或https://前缀。抄送地址支持IP和域名。
-
2、输入有效的地址后,请点击确定按钮进行地址校验。校验的过程是云信服务器会往该地址post一条内容为空的请求(header见下方抄送HTTP Header定义,body为json格式,内容为空,即{}),如果在5秒内收到该地址返回HTTP 200状态码,则校验成功。若发现校验不成功,可以使用postman等工具辅助自测。
-
3、地址校验通过后,可以勾选需要的抄送信息。部分功能需要联系商务经理申请开通后方可勾选,详见各自抄送类型的说明。完成后,当应用内发生对应的事件或产生对应的消息,开发者即可通过解析来自云信的post请求来获取内容。
-
4、消息抄送的超时时间是5秒,如果在5秒内收到开发者服务器返回HTTP 200或HTTP 500状态码,则认为消息抄送成功,否则认为抄送失败。如果开发者在接收到消息之后,会做比较耗时的操作(例如DB入库等)的话,建议将该接口做成异步机制(例如可以将消息先存到MQ中),以免被网易云信判定为超时。若有特殊需求不能丢弃消息抄送,请联系商务经理开通高保障抄送。开通后,如果抄送失败,网易云信服务器将会尝试重新抄送一定次数(目前最多1000次)。如果开发者接收消息抄送的接口在一段时间内持续无法响应,网易云信服务器支持将最多50万条消息缓存下来,待开发者消息消息接收接口恢复后,手动提交任务重新进行抄送。
-
5、考虑到网络环境的不稳定,为了确保开发者的消息接口能收到抄送的消息,网易云信可能会重复发送同一条消息,建议开发者对所收到的消息与事件通知进行一定的去重操作。
-
6、在消息抄送服务中,post请求的header中的CheckSum = sha1(AppSecret + MD5 + CurTime), 其中AppSecret 、MD5、CurTime均为String类型。在验证数据是否在传输过程中被篡改时,需要计算验证MD5值是否被修改,以及计算验证CheckSum。AppSecret值为开发者的AppSecret(请勿与AppKey混淆), MD5值为根据request body计算出来的值, 即MD5 = md5(request body)。
-
7、特别的,对于部分抄送类型(如API发消息、SDK发消息等),可以在发消息的参数里设置环境变量,服务器将根据环境抄送到不同的地址,环境和抄送地址的映射关系配置请联系商务经理。
-
8、抄送的Content-Type为application/json,而非application/x-www-form-urlencoded,请注意区分解析方式。
回调地址设置
- 登录云信控制台在选择开通云呼叫中心的应用,在云呼叫中心功能栏选择“号码管理”,在“消息回调地址配置”填写需要抄送消息的服务器地址
抄送HTTP Header定义
Header | 参数说明 |
AppKey | 开发者平台分配的appkey |
CurTime | 当前UTC时间戳,从1970年1月1日0点0 分0 秒开始到现在的毫秒数 |
CheckSum | sha1(AppSecret + MD5 + CurTime) |
MD5 | md5(request body) |
- MD5值计算举例
String requestBody = "{}";
String MD5 = CheckSumBuilder.getMD5(requestBody);
- CheckSum值计算举例
String AppSecret = "90u757h67n87"; //注意:此处是AppSecret,不是AppKey。
String MD5 = "9894907e4ad9de4678091277509361f7";
String CurTime = "1440570500855"; ////当前UTC时间戳,从1970年1月1日0点0 分0 秒开始到现在的毫秒数(String)
String CheckSum = CheckSumBuilder.getCheckSum(AppSecret, MD5, CurTime); //参考 接口概述 -> API checksum校验 部分
计算CheckSum的java代码举例如下
package com.netease.nim.route;
import com.alibaba.fastjson.JSONObject;
import com.netease.nim.route.CheckSumBuilder;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Controller
@RequestMapping(value = {"/route"})
public class RouteController {
public static final Logger logger = LoggerFactory
.getLogger(RouteController.class);
// 需要改成自身应用的appSecret
private final String appSecret = "7bb79g40f44j";
@RequestMapping(value = {"/mockClient.action"}, method = {RequestMethod.POST})
@ResponseBody
public JSONObject mockClient(HttpServletRequest request)
throws Exception {
JSONObject result = new JSONObject();
try {
// 获取请求体
byte[] body = readBody(request);
if (body == null) {
logger.warn("request wrong, empty body!");
result.put("code", 414);
return result;
}
// 获取部分request header,并打印
String ContentType = request.getContentType();
String AppKey = request.getHeader("AppKey");
String CurTime = request.getHeader("CurTime");
String MD5 = request.getHeader("MD5");
String CheckSum = request.getHeader("CheckSum");
logger.info("request headers: ContentType = {}, AppKey = {}, CurTime = {}, " +
"MD5 = {}, CheckSum = {}", ContentType, AppKey, CurTime, MD5, CheckSum);
// 将请求体转成String格式,并打印
String requestBody = new String(body, "utf-8");
logger.info("request body = {}", requestBody);
// 获取计算过的md5及checkSum
String verifyMD5 = CheckSumBuilder.getMD5(requestBody);
String verifyChecksum = CheckSumBuilder.getCheckSum(appSecret, verifyMD5, CurTime);
logger.debug("verifyMD5 = {}, verifyChecksum = {}", verifyMD5, verifyChecksum);
// TODO: 比较md5、checkSum是否一致,以及后续业务处理
result.put("code", 200);
return result;
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
result.put("code", 414);
return result;
}
}
private byte[] readBody(HttpServletRequest request) throws IOException {
if (request.getContentLength() > 0) {
byte[] body = new byte[request.getContentLength()];
IOUtils.readFully(request.getInputStream(), body);
return body;
} else
return null;
}
}
package com.netease.nim.route;
import java.security.MessageDigest;
public class CheckSumBuilder {
// 计算并获取CheckSum
public static String getCheckSum(String appSecret, String nonce, String curTime) {
return encode("sha1", appSecret + nonce + curTime);
}
// 计算并获取md5值
public static String getMD5(String requestBody) {
return encode("md5", requestBody);
}
private static String encode(String algorithm, String value) {
if (value == null) {
return null;
}
try {
MessageDigest messageDigest
= MessageDigest.getInstance(algorithm);
messageDigest.update(value.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
}