PAYATHON 2026

支付宝沙箱环境授权demo失败,授权字符串如何获取?

支付阿杰

结论

“授权字符串”不是从支付宝后台复制或下载的固定值,而是商户服务端根据应用信息、授权参数和应用私钥生成并签名的 authInfo。客户端将其传给支付宝 SDK。用户完成登录授权后,SDK 会返回临时 auth_code,再由服务端用该授权码换取用户授权信息或访问令牌。

更换 target_id 后仍然报错,通常意味着问题不在 target_id。建议重点检查:

  • 是否使用了沙箱应用的 app_id、商户 PID、密钥和沙箱买家账号
  • 应用私钥是否与开放平台配置的应用公钥匹配
  • authInfo 的参数、排序、字符编码和签名算法是否正确
  • 客户端是否确实收到了重新生成的授权字符串
  • SDK 返回的 resultStatus、result_code 和 memo 分别是什么
  • 当前沙箱应用是否具备相应的账户授权能力

授权字符串和授权码不是一回事

支付宝账户授权流程中,authInfo 和 auth_code 很容易混淆。

authInfo

authInfo 是调用支付宝客户端 SDK 前需要准备的授权请求字符串,一般包含:

  • app_id
  • pid
  • target_id
  • auth_type
  • scope
  • product_id
  • sign_type
  • sign

具体参数应以当前账户授权产品文档和所用 SDK 示例为准。由于 authInfo 需要使用应用私钥签名,所以应由商户服务端生成。不要把私钥放在客户端,也不要让客户端自行签名。

auth_code

auth_code 是用户在支付宝页面确认授权后返回的临时授权码。它不是提前申请的,也不是由开放平台后台提供的。

服务端收到 auth_code 后,通常需要调用对应接口来换取令牌。不同授权产品使用的接口和参数可能不同。例如,网页 OAuth 场景中常见的接口是:

alipay.system.oauth.token

因此,SDK 调用前使用的 authInfo 与授权成功后返回的 auth_code 是两个不同的概念。

正确的处理流程

完整流程通常如下:

  1. 客户端向商户服务端请求授权参数。
  2. 服务端根据沙箱应用配置生成授权参数。
  3. 服务端使用应用私钥完成 RSA2 签名,并返回完整的 authInfo。
  4. 客户端调用支付宝授权 SDK。
  5. 用户使用沙箱买家账号登录并确认授权。
  6. 客户端解析 SDK 返回结果,将 auth_code 发给服务端。
  7. 服务端通过沙箱网关完成后续的授权码交换。

私钥的使用和授权码交换都应放在服务端完成。

Java 生成授权字符串示例

下面是常见的服务端代码组织方式,其中的参数仅用于说明流程。实际参数名称和必填项应以当前使用的支付宝授权 SDK 示例为准。

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

public class AlipayAuthInfoBuilder {

    public static String buildAuthInfo(
            String appId,
            String pid,
            String targetId,
            String appPrivateKey) throws Exception {

        Map<String, String> params = new TreeMap<>();
        params.put("apiname", "com.alipay.account.auth");
        params.put("method", "alipay.open.auth.sdk.code.get");
        params.put("app_id", appId);
        params.put("app_name", "mc");
        params.put("biz_type", "openservice");
        params.put("pid", pid);
        params.put("product_id", "APP_FAST_LOGIN");
        params.put("scope", "kuaijie");
        params.put("target_id", targetId);
        params.put("auth_type", "AUTHACCOUNT");
        params.put("sign_type", "RSA2");

        String unsignedContent = buildQuery(params);

        // 此处应调用支付宝官方 SDK 的 RSA2 签名方法。
        // 不建议在业务代码中自行实现 RSA 签名细节。
        String sign = signWithAlipaySdk(
                unsignedContent,
                appPrivateKey,
                "UTF-8",
                "RSA2"
        );

        return unsignedContent
                + "&sign="
                + URLEncoder.encode(sign, StandardCharsets.UTF_8.name());
    }

    private static String buildQuery(Map<String, String> params) {
        StringBuilder result = new StringBuilder();

        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (result.length() > 0) {
                result.append("&");
            }

            result.append(entry.getKey())
                  .append("=")
                  .append(entry.getValue());
        }

        return result.toString();
    }

    private static String signWithAlipaySdk(
            String content,
            String privateKey,
            String charset,
            String signType) {

        // 使用当前版本支付宝服务端 SDK 提供的签名工具实现。
        throw new UnsupportedOperationException("请接入支付宝官方 SDK 签名方法");
    }
}

示例没有手写 RSA2 的具体实现。签名内容是否需要编码、哪些字段参与签名,以及最终参数如何进行 URL 编码,都必须与当前 SDK 的官方示例一致。直接复用官方示例中的参数构造方式和签名工具类更稳妥。

客户端收到服务端返回的 authInfo 后,再调用授权 SDK。例如,Android 中常见的调用方式是:

AuthTask authTask = new AuthTask(activity);
Map<String, String> result = authTask.authV2(authInfo, true);

String resultStatus = result.get("resultStatus");
String resultData = result.get("result");
String memo = result.get("memo");

不能只根据页面上的”授权失败”判断问题。排查时应保存完整的 resultStatus、result 和 memo,同时遮盖日志中的签名、授权码等敏感信息。

授权失败的排查顺序

1. 确认环境没有混用

进行沙箱测试时,整条链路都应使用沙箱配置:

  • 沙箱应用的 app_id
  • 沙箱账号对应的 PID
  • 沙箱应用配置的密钥
  • 沙箱买家账号
  • 沙箱网关
  • 与沙箱环境兼容的客户端或测试方式

支付成功只能说明支付链路的配置基本可用,并不能证明账户授权产品的权限、参数和签名同样正确。

尤其需要排除以下混用情况:

沙箱 app_id + 正式环境私钥
沙箱 app_id + 正式环境 PID
沙箱授权请求 + 正式支付宝买家账号
客户端使用新 authInfo + 服务端仍返回旧缓存

2. 检查密钥对应关系

RSA2 签名的对应关系应为:

应用私钥签名
        ↓
开放平台中配置的应用公钥验签

常见问题包括:

  • 把支付宝公钥误当成应用公钥
  • 上传公钥后,服务端仍在使用另一对密钥中的私钥
  • 重新生成密钥后,没有同步更新相关配置
  • 私钥格式错误,或者读取时丢失了换行
  • 平台配置的是 RSA2,代码却使用 RSA 签名
  • 签名字符集与请求字符集不一致

如果支付和授权使用同一个应用及同一套签名配置,支付成功可以帮助缩小密钥问题的排查范围,但仍需确认授权字符串是否按照正确规则签名。

3. 检查 target_id

target_id 是商户生成的请求标识,不是支付宝提供的用户 ID。它应满足以下要求:

  • 在商户系统中具有唯一性
  • 长度和字符范围符合当前接口要求
  • 不包含空格、中文或未经处理的特殊字符
  • 服务端确实使用了本次传入的新值
  • 没有被缓存、常量或旧安装包中的值覆盖

可以使用 UUID 生成:

String targetId = java.util.UUID.randomUUID()
        .toString()
        .replace("-", "");

如果更换 target_id 后仍然返回完全相同的错误,问题更可能出在签名、应用权限、账号环境或客户端缓存上。

4. 检查账户授权产品权限

支付能力和账户授权能力属于不同的产品能力。需要在开放平台应用配置中逐项确认:

  • 当前应用是否添加了账户授权相关能力
  • 沙箱是否支持正在测试的授权方式
  • 应用类型是否符合该授权产品的要求
  • 授权回调地址或安全配置是否正确
  • 应用配置变更是否已经生效

部分能力在沙箱和正式环境中的行为可能不同。如果文档没有明确说明沙箱支持情况,应以当前开放平台控制台、对应产品文档和支付宝技术支持的反馈为准。不能仅凭支付成功,就认定授权功能一定可用。

5. 输出完整 SDK 返回结果

至少记录以下内容:

Log.d("AlipayAuth", "resultStatus=" + result.get("resultStatus"));
Log.d("AlipayAuth", "memo=" + result.get("memo"));
Log.d("AlipayAuth", "result=" + sanitize(result.get("result")));

排查需要具体错误码,不能只看”授权失败”这段页面提示。同一条提示可能对应参数错误、签名失败、应用无权限、账号不匹配或授权码失效等不同问题。

6. 清理缓存并确认请求已更新

如果第一次登录失败后,后续请求的表现始终相同,可以依次检查:

  • 卸载并重新安装测试应用
  • 清除支付宝沙箱客户端的数据
  • 重新登录沙箱买家账号
  • 关闭客户端对 authInfo 的缓存
  • 在服务端打印本次 target_id 和签名原文摘要
  • 确认设备上安装的是最新编译包
  • 检查异步回调是否误用了上一次的结果

不要在日志中直接打印应用私钥,也不要长期保存完整的授权字符串。

如果使用的是网页 OAuth

如果实际接入的不是客户端 AuthTask,而是网页授权,通常不需要前面所述的 SDK authInfo。商户需要构造授权跳转地址。用户确认授权后,支付宝通过回调地址返回 auth_code。

授权地址示意如下:

https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm
    ?app_id=你的沙箱应用ID
    &scope=auth_user
    &redirect_uri=URL编码后的回调地址
    &state=商户自定义状态值

回调地址示意如下:

https://example.com/alipay/callback
    ?app_id=...
    &source=alipay_wallet
    &scope=auth_user
    &auth_code=...
    &state=...

网页授权和客户端账户授权是两条不同的链路。接入前应先确认所用 demo 属于哪一种,再根据对应文档构造请求。不要混用网页授权地址、客户端 authInfo 和支付订单字符串。

注意事项

  • 应用私钥只能保存在可信的服务端,不能打包进 Android 或 iOS 客户端。
  • auth_code 通常是临时凭证,取得后应立即交给服务端处理,不能重复使用。
  • target_id 只用于标识授权请求,不能代替用户 ID 或授权码。
  • 参数值在签名前后的 URL 编码规则必须与官方 SDK 保持一致。
  • 客户端 SDK、服务端 SDK 和示例代码应来自同一时期的文档,以免混用旧参数。
  • 沙箱账号、正式账号、沙箱网关和正式网关必须严格隔离。
  • 对外求助时,应提供错误码、resultStatus、脱敏后的 memo 和请求环境,不要提供私钥、完整签名或有效授权码。

备注:内容仅供参考。