关于淘宝奇门接口验签问题

淘宝奇门接口验签实战与解决办法
本文档分享了在对接淘宝奇门接口时遇到的验签问题及解决方案。在接口配置过程中,发现本地POST请求加入验签后返回验签失败。问题在于奇门接口验签代码无法正确获取请求的body内容。通过调整验签方法中获取body的方式,成功实现了本地验签。验签注意事项包括:使用正确的编码UTF8,核对密钥,确保输入流未被提前读取,以及避免上层框架对参数的改变。若遇到问题,可提供验签前的字符串给官方协助排查。

最近做了一个奇门接口对接问题。遇到了验签问题,特和大家分享下。

目前的需求是在奇门发布一个接口。本地接口是post请求,参数在body中存储。

奇门的接口配置流程可以参考官方文档如下链接内容:

开放平台-文档中心

奇门官方集成接入说明

开放平台-文档中心

下面说一说我的接口配置大概情况:

例如我本地有一个post请求,带参数的

 当前请求在服务接口没有加入淘宝奇门验签的时候,是正常请求和返回的。

当加入淘宝估计方法验签后,应该是返回验签失败。入下图显示:

 以上的返回格式是奇门接口要求的验签返回错误格式,参考如下的工具类代码格式

/**
* 使用该方法同时请务必要阅读该方法的源码,大致了解该方法的实现。
*
* 如果验签失败则需要返回验签失败的结果,并且需要和配置对应的上,系统才认为是验签成功;
*
* 如果正确的请求老是误认为验签错误了,则确认以下几点:1编码是否UTF82 2密钥是否写错了 3request如果是json,xml类型则(form则忽略)确认inputstream是否被读取过了?如果需要使用body但不想改动麻烦,可以先执行验签,
* 然后在验签结果中获取body(checkResult.getRequestBody()方法)来执行业务逻辑
*
*/
CheckResult result = SpiUtils.checkSign(request, targetAppSecret);? //这里执行验签逻辑
if(!result.isSuccess()) {  //如果验签失败则需要返回 验签失败的结果,并且需要和配置对应的上,系统才认为是验签成功
   HttpSampleResponse httpSampleResponse = new HttpSampleResponse();
   httpSampleResponse.setErrorMessage("Illegal request");
   httpSampleResponse.setErrorCode("sign-check-failure");
   httpSampleResponse.setFlag("failure);
   //return
}

想本地验证验签的成功与否,可以去奇门日志中查找对应的请求记录,找到对应内容放入本地posman中测试本地方法。

通过以上测试发现问题:使用奇门测试工具的转发url(已加签)和报文,在本地无法验证通过。

通过反复尝试,发现只要是本地body带内容的无法通过验签:

主要我现在用测试的posman的body参数值有,但是使用淘宝的验签代码里面的这段看了下获取不到body的内容

String body = WebUtils.getStreamAsString(request.getInputStream(), charset);这个body内容是空的。

我本地的接口代码如下:

    @POST
    @Path("authenticationQMPost")
    @Produces(MediaType.APPLICATION_JSON)
    public String authenticationQMPost(
            String params,
            @Context HttpServletRequest request,
            @Context HttpServletResponse response) {
        //设置响应头编码格式
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json; charset=UTF-8");
        Map<String, Object> resultMap = FastMap.newInstance();
        PrintWriter out = null;
        String resultStr = "";
        try {
            out = response.getWriter();
            request.setCharacterEncoding("utf-8");
            CheckResult result =  QiMenUtils.checkSign(request, targetAppSecret, params);//这里执行验签逻辑
            if (!result.isSuccess()) {  //如果验签失败则需要返回 验签失败的结果,并且需要和配置对应的上,系统才认为是验签成功
                resultStr = authenticationFailure(result.getRequestBody());
            } else {
                if (null == params || "" == params) {
                    resultStr = authenticationSuccess("test post succ,body无内容");
                } else {
                    resultStr = authenticationSuccess("test post succ,body内容:" + params);
                }
            }
        } catch (Exception e) {
            if (Debug.errorOn()) {
                Debug.logError(e, module);
            }
            log.error("", e);
            resultStr = authenticationFailure(e.getMessage());
        }

        out.println(resultStr);
        out.flush();
        out.close();
        return null;
    }

由于我本地的方法的params是可以获取到body的内容,所以确定修改淘宝奇门的验签方法中的获取body的方法。

public static CheckResult checkSign(HttpServletRequest request, String secret, String bodyStr) throws IOException {
        CheckResult result = new CheckResult();
        String ctype = request.getContentType();
        String charset = WebUtils.getResponseCharset(ctype);
        if (null != ctype && !"".equals(ctype)) {
            if (!ctype.startsWith("application/json") && !ctype.startsWith("text/xml") && !ctype.startsWith("application/xml") && !ctype.startsWith("text/plain")) {
                if (!ctype.startsWith("application/x-www-form-urlencoded")) {
                    throw new RuntimeException("Unspported SPI request");
                }

                boolean valid = checkSignInternal(request, (Map) null, (String) null, secret, charset);
                result.setSuccess(valid);
            } else {
                //String body = WebUtils.getStreamAsString(request.getInputStream(), charset);
                boolean valid = checkSignInternal(request, (Map) null, bodyStr, secret, charset);
                result.setSuccess(valid);
                result.setRequestBody(bodyStr);
            }
        }  else {
            //String body = WebUtils.getStreamAsString(request.getInputStream(), charset);
            boolean valid = checkSignInternal(request, (Map) null, bodyStr, secret, charset);
            result.setSuccess(valid);
            result.setRequestBody(bodyStr);
        }

        return result;
    }

通过以上调整,从奇门接口日志中拿到的“转发url参数”和“奇门转发报文” 可以正常通过本地的方法验签。

使用验签注意:

* 使用Sdk的SpiUtils.checkSign来执行验签,同时请务必要阅读该方法的源码,大致了解该方法的实现。
*
* 如果验签失败则需要返回验签失败的结果,并且需要和配置对应的上,系统才认为是验签成功;
*
* 如果正确的请求误认为验签错误了,则确认以下几点:
* 1编码是否UTF8
* 2密钥是否写错了,特别是沙箱和线上密钥别搞错
* 3request如果是json,xml类型(form不用理会)则确认inputstream是否被读取过了?如果需要使用body但不想改动麻烦,可以先执行验签,
* 然后在验签结果中获取body(checkResult.getRequestBody()方法)来执行业务逻辑
* 4上层框架是否有做封装导致参数变更
* 如果名以上几点检查没问题,请提供sdk md5前(SpiUtils 类里的方法 String sign(Map<String, String> params, String body, String secret, String charset)
)的最终字符串给小二协助排查

### 奇门对接实现方法 在 .NET 环境中进行奇门对接的操作,需要特别注意返回的 `ContentType` 以及 SDK 的限制。根据提供的代码片段和描述,以下是对实现方法的详细说明。 #### 1. 逻辑分析 奇门测试环境返回的 `ContentType` 为 `application/xml`,而 SDK 的方法中对 `ContentType` 进行了严格的判断[^1]。如果返回的 `ContentType` 不符合预设值(如 `application/json`、`text/xml` 或 `text/plain`),则会抛出异常 `Unspported SPI request`。因此,在实现时,需要确保请求的 `ContentType` 被正确处理。 #### 2. 方法实现 以下是基于 .NET 的方法实现: ```csharp using System; using System.Net; using System.Text; public class SpiSignChecker { public static CheckResult CheckSign(HttpRequest request, string secret) { CheckResult result = new CheckResult(); string ctype = request.ContentType; if (ctype.StartsWith("application/json") || ctype.StartsWith("text/xml") || ctype.StartsWith("text/plain")) { // 获取请求体内容并进行内部校 result.Body = GetStreamAsString(request, GetRequestCharset(ctype)); result.Success = CheckSignInternal(request, result.Body, secret); } else if (ctype.StartsWith("multipart/form-data")) { // 处理 form-data 类型请求 result.Success = CheckSignInternal(request, null, secret); } else { throw new Exception("Unsupported SPI request"); } return result; } private static bool CheckSignInternal(HttpRequest request, string body, string secret) { // 实现具体的证逻辑 string signature = request.Headers["Signature"]; string expectedSignature = GenerateSignature(body, secret); return signature == expectedSignature; } private static string GenerateSignature(string body, string secret) { // 根据业务规则生成名 using (var sha256 = System.Security.Cryptography.SHA256.Create()) { byte[] bytes = Encoding.UTF8.GetBytes(body + secret); byte[] hashBytes = sha256.ComputeHash(bytes); return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); } } private static string GetStreamAsString(HttpRequest request, string charset) { // 将请求体转换为字符串 using (var reader = new System.IO.StreamReader(request.InputStream, Encoding.GetEncoding(charset))) { return reader.ReadToEnd(); } } private static string GetRequestCharset(string contentType) { // 提取 Content-Type 中的字符集 if (string.IsNullOrEmpty(contentType)) return "UTF-8"; var parts = contentType.Split(';'); foreach (var part in parts) { if (part.Trim().StartsWith("charset", StringComparison.OrdinalIgnoreCase)) { var charsetParts = part.Split('='); if (charsetParts.Length == 2) return charsetParts[1].Trim(); } } return "UTF-8"; } } public class CheckResult { public bool Success { get; set; } public string Body { get; set; } } ``` #### 3. 注意事项 - **ContentType 处理**:如果奇门测试环境返回的 `ContentType` 为 `application/xml`,需要在 SDK 中扩展支持该类型,或者通过修改请求头的方式将 `ContentType` 设置为支持的类型。 - **名算法**:名生成逻辑需与奇门平台保持一致,通常使用 HMAC-SHA256 或类似的算法[^1]。 - **沙箱环境**:在开发阶段可以使用平台提供的沙箱 `APP Key` 和 `Secret` 进行测试[^2]。 #### 4. 测试建议 在完成实现后,可以通过以下步骤进行测试: - 使用不同的 `ContentType` 测试方法的兼容性。 - 名生成逻辑是否与奇门平台一致。 - 在沙箱环境中模拟真实的接口调用场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值