支付宝沙箱环境授权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_idpidtarget_idauth_typescopeproduct_idsign_typesign
具体参数应以当前账户授权产品文档和所用 SDK 示例为准。由于 authInfo 需要使用应用私钥签名,所以应由商户服务端生成。不要把私钥放在客户端,也不要让客户端自行签名。
auth_code
auth_code 是用户在支付宝页面确认授权后返回的临时授权码。它不是提前申请的,也不是由开放平台后台提供的。
服务端收到 auth_code 后,通常需要调用对应接口来换取令牌。不同授权产品使用的接口和参数可能不同。例如,网页 OAuth 场景中常见的接口是:
alipay.system.oauth.token
因此,SDK 调用前使用的 authInfo 与授权成功后返回的 auth_code 是两个不同的概念。
正确的处理流程
完整流程通常如下:
- 客户端向商户服务端请求授权参数。
- 服务端根据沙箱应用配置生成授权参数。
- 服务端使用应用私钥完成 RSA2 签名,并返回完整的
authInfo。 - 客户端调用支付宝授权 SDK。
- 用户使用沙箱买家账号登录并确认授权。
- 客户端解析 SDK 返回结果,将
auth_code发给服务端。 - 服务端通过沙箱网关完成后续的授权码交换。
私钥的使用和授权码交换都应放在服务端完成。
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和请求环境,不要提供私钥、完整签名或有效授权码。
备注:内容仅供参考。
