为了保护某些敏感的数据的安全性,我们提供了一套全新的接口请求方式:SPI。目前已开放的接口有购物车回调、手淘专用红包发放等,后续会有更多的接口会通过这种方式开放。
把互动应用后台appkey提供给SPI的发布人(
书通
)。
注:“购物车回调”,“颁发onecode”两个场景已经默认开放,无需此步骤
(例如:http://my.open.taobao.com/spi/groups.htm?appkey=23025543&app_id=1744060 )
申请之后,需要主动通知一下SPI提供者(
书通
)进行审批。
在TOP控制台“我的场景”,点击进入开发
点击“开发测试”,进入页面后配置自己的后台页面地址,该页面地址用来接收 数据请求
开始页面开发,比如测试页面链接
http://jiuxianphone-1.play.admin.jaeapp.com/spiCart.jsp
页面格式输出格式(json或者xml)以及页面输出参数需要参照场景开发文档。
页面JSP示例代码如下:
<%@ page contentType="application/json; charset=UTF-8" %>
<%
response.setContentType("text/xml");
%>
<recieved>true</recieved>
账号:b2ctest17@yahoo.cn 密码:sxc50113891
页面PHP示例代码如下:待补充。
坐等接收请求,程序需要有servlet来处理该Http get请求,TOP会发送一条以下格式的Http get请求(以购物车接口为例):
验签代码SignUtil中的secret字段需要根据应用配置填写。
package tae.util;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.*;
public class SignUtil {
private static final Logger logger = Logger.getRootLogger();
private static final String HEADER_PARAM_PREFIX = "header_";
private static final String TOP_SIGN_LIST = "top_sign_list";
private static final String SECRET = /*根据应用secret配置信息填写*/;
public static ThreadLocal<String> body = new ThreadLocal<String>();
/**
* @param headerMap http请求中header中的参数,包括”top_sign_list“域,这个域中用”,"连接了所有需要参与sign的header参数;
* 如果某参数没有值,也需要放入headerMap中,相应的values为""
* @param queryMap http请求中query中的参数
* @param body 从http请求中取出的输入流(可参考getBody方法)
* @param secret 签名密钥
* @param encode 服务的编码方式(在配置服务url的页面中可以看到)
* @return
* @throws Exception
*/
public static String sign(Map<String, String> headerMap, Map<String, String> queryMap, String body, String secret, String encode) throws Exception {
//取出header中的top_sign_list,它的内容是用","隔开的参数名,这些参数需要参与sign
//这么做的原因是header有可能会被改写
String topSignList = headerMap.get(TOP_SIGN_LIST);
if (StringUtils.isNotBlank(topSignList)) {
String[] headerSignParams = StringUtils.split(topSignList, ",");
for (int i = 0; i < headerSignParams.length; i++) {
String headerSignParam = headerSignParams[i];
String value = headerMap.get(headerSignParam);
//为了避免与query中的参数同名,加上前辍
queryMap.put(HEADER_PARAM_PREFIX + headerSignParam, value);
}
}
String sign = signatureForSpi(queryMap, secret, true, false, body, encode);
return sign;
}
public static String getBody(HttpServletRequest request) {
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
String dataOrigin = IOUtils.toString(inputStream);
return dataOrigin;
} catch (Exception e) {
logger.info("exception IOUtils.toString");
throw new RuntimeException(e);
}
}
private static String signatureForSpi(Map<String, String> params, String secret,
boolean appendSecret, boolean isHMac, String body, String encode) throws Exception {
StringBuilder sb = new StringBuilder();
// append if not hmac
if (!isHMac) {
sb.append(secret);
}
if (params != null && !params.isEmpty()) {
String[] names = params.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
Arrays.sort(names);
for (int i = 0; i < names.length; i++) {
String name = names[i];
sb.append(name);
sb.append(params.get(name));
}
}
if (StringUtils.isNotBlank(body)) {
sb.append(body);
}
if (appendSecret && !isHMac) {
sb.append(secret);
}
String sign = null;
try {
//md5
sign = DigestUtils.md5Hex(sb.toString().getBytes(encode)).toUpperCase();
} catch (Exception e) {
logger.info("exception DigestUtils.md5Hex");
throw new RuntimeException(e);
}
return sign;
}
private static String date2ymdhms(Date date) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return f.format(date);
}
public static Boolean testSign(HttpServletRequest request, Map<String, Boolean> decodeConfig) {
Map<String, String> headerMap = getHeadersInfo(request);
//获得 UrlDecode后的header和body参数
Map<String, String> headers = new HashMap<String, String>();
String body = null;
try {
if (headerMap != null && !headerMap.isEmpty()) {
String[] headerParams = headerMap.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
for (int i = 0; i < headerParams.length; i++) {
String name = headerParams[i];
headers.put(name, URLDecoder.decode(headerMap.get(name), "UTF-8"));
}
}
body = URLDecoder.decode(getBody(request), "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.info(JSONObject.toJSONString(e.getStackTrace()));
}
//获得query参数,使用request.getParameterMap获得的参数有乱码
Map<String, String> params = new HashMap<String, String>();
String queryString = request.getQueryString();
String[] param = StringUtils.split(queryString, "&");
for (int i = 0; null != param && i < param.length; i++) {
String[] kv = StringUtils.split(param[i], "=");
if (kv.length == 2) {
params.put(kv[0], kv[1]);
}
}
Map<String, String> queryMap = new HashMap<String, String>();
String sign = "";
sign = params.get("sign");
logger.info("sign : " + sign);
copyMap(params, queryMap);
logger.info("queryMap : " + JSONObject.toJSONString(queryMap));
//签名
String sign2 = "";
try {
sign2 = sign(headers, queryMap, body, SECRET, "UTF-8");
} catch (Exception e) {
logger.info(JSONObject.toJSONString(e.getStackTrace()));
}
logger.info("sign2 : " + sign2);
return sign2.equals(sign);
}
private static void copyMap(Map<String, String> params, Map<String, String> queryMap) {
for (String p : params.keySet()) {
if ("sign".equals(p)) {
continue;
}
String pValue = params.get(p);
try {
pValue = URLDecoder.decode(pValue, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.info(JSONObject.toJSONString(e.getStackTrace()));
}
queryMap.put(p, pValue);
}
}
private static Map<String, String> getHeadersInfo(HttpServletRequest request) {
Map<String, String> map = new HashMap<String, String>();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}