OpenID 是微信支付识别用户身份的核心凭证,JSAPI/小程序/公众号支付均需先获取。本文详解 **静默授权 (snsapi_base)** 获取 OpenID 全流程:商户配置、授权链接构建、回调处理、授权码换取 access_token/openid、错误码对照与安全最佳实践。
pay.example.com)。https://pay.example.com/pay/)。
用户点击支付按钮
│
▼
前端/后端构造授权链接 → 重定向微信授权页
│
▼
用户静默授权 (snsapi_base 无弹窗)
│
▼
微信回调 redirect_uri?code=AUTH_CODE&state=STATE
│
▼
后端拿 code + appid + secret → 请求 https://api.weixin.qq.com/sns/oauth2/access_token
│
▼
微信返回 { access_token, expires_in, refresh_token, openid, scope, unionid }
│
▼
后端存储 openid → 继续统一下单
public class WeChatOAuth2Util {
private static final String AUTH_URL =
"https://open.weixin.qq.com/connect/oauth2/authorize?" +
"appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
/** 生成静默授权链接 (仅获取 openid,无弹窗) */
public static String buildSilentAuthUrl(String appid, String redirectUri, String state) {
String encodedRedirect = URLEncoder.encode(redirectUri, StandardCharsets.UTF_8);
return String.format(AUTH_URL, appid, encodedRedirect, "snsapi_base", state);
}
/** 生成用户信息授权链接 (需用户点击确认,可获取昵称/头像) */
public static String buildUserInfoAuthUrl(String appid, String redirectUri, String state) {
String encodedRedirect = URLEncoder.encode(redirectUri, StandardCharsets.UTF_8);
return String.format(AUTH_URL, appid, encodedRedirect, "snsapi_userinfo", state);
}
}
关键参数:
scope=snsapi_base:静默授权,**用户无感知**,仅返回 openid,支付首选。scope=snsapi_userinfo:弹窗授权,返回 openid + 用户信息 (需 access_token 再调 /sns/userinfo)。state:CSRF 防护/携带业务参数 (如订单 ID),回调原样返回,**必须校验**。redirect_uri:必须在公众号后台配置的授权回调域名下,**需 URLEncode**。
@RestController
@RequestMapping("/wx/oauth")
public class WeChatOAuthController {
@Value("${wechat.appid}") String appid;
@Value("${wechat.appsecret}") String appsecret;
@GetMapping("/callback")
public void callback(@RequestParam("code") String code,
@RequestParam("state") String state,
HttpServletResponse response) throws IOException {
// 1. 校验 state (防 CSRF / 恢复业务上下文)
if (!stateService.validateAndConsume(state)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "非法 state");
return;
}
// 2. 请求微信接口换取 access_token + openid
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" + appid + "&secret=" + appsecret +
"&code=" + code + "&grant_type=authorization_code";
try {
String resp = httpClient.get(url); // 同步 HTTP GET
JSONObject json = JSON.parseObject(resp);
// 3. 错误处理
if (json.containsKey("errcode")) {
int errCode = json.getIntValue("errcode");
String errMsg = json.getString("errmsg");
log.error("获取 openid 失败: code={}, errCode={}, errMsg={}", code, errCode, errMsg);
handleOAuthError(errCode, response);
return;
}
// 4. 成功解析
String openid = json.getString("openid");
String accessToken = json.getString("access_token");
String refreshToken = json.getString("refresh_token");
int expiresIn = json.getIntValue("expires_in"); // 7200 秒
// 5. 业务处理:关联订单、存储 openid、跳转支付页
String orderId = stateService.getOrderId(state); // 从 state 解析
orderService.bindOpenId(orderId, openid);
// 6. 重定向到支付确认页 (携带 openid 或订单号)
response.sendRedirect("/pay/confirm?orderId=" + orderId);
} catch (Exception e) {
log.error("OAuth 回调异常", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private void handleOAuthError(int errCode, HttpServletResponse response) throws IOException {
switch (errCode) {
case 40029: // invalid code
response.sendRedirect("/pay/error?msg=授权码无效或已过期");
break;
case 40003: // invalid openid
response.sendRedirect("/pay/error?msg=用户未关注公众号");
break;
case 48001: // api unauthorized
response.sendRedirect("/pay/error?msg=公众号无网页授权权限");
break;
default:
response.sendRedirect("/pay/error?msg=授权失败:" + errCode);
}
}
}
wx.login |
| 40163 | code been used | code 重复使用 | 确保回调幂等,code 用完即失效 |
| 48001 | api unauthorized | 公众号无网页授权权限 | 认证公众号/服务号,配置授权回调域名 |
| 40001 | invalid credential | appid/appsecret 错误 | 核对公众号后台开发配置 |
| 40013 | invalid appid | appid 不存在 | 核对公众号/小程序 AppID |
wx_access_token:{appid},提前 5 分钟刷新。| 场景 | 获取 openid 方式 | 关键接口 |
|---|---|---|
| 公众号网页 (H5) | OAuth2.0 网页授权 | /sns/oauth2/authorize → /sns/oauth2/access_token |
| 小程序 | wx.login → code → 后端 jscode2session | auth.code2Session (AppID+AppSecret+js_code) |
| APP (原生) | 微信开放平台授权登录 | /sns/oauth2/access_token (同网页,但回调 Scheme) |
获取 OpenID 是微信支付的**入口关卡**。掌握 **snsapi_base 静默授权**、**code 单次使用**、**state 防 CSRF**、**后端代理换取**、**错误码精准处理** 五大核心要点,配合 Redis 缓存 access_token/refresh_token,即可构建高可用、安全的授权基础设施。