首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

微信支付 V3 获取用户 OpenID 完整流程 (静默授权 + 授权码换取)

     編輯:Java 相關知識

OpenID 是微信支付识别用户身份的核心凭证,JSAPI/小程序/公众号支付均需先获取。本文详解 **静默授权 (snsapi_base)** 获取 OpenID 全流程:商户配置、授权链接构建、回调处理、授权码换取 access_token/openid、错误码对照与安全最佳实践。

前置配置:微信公众平台 / 商户平台

  1. **公众号/小程序后台** → 开发 → 接口权限 → 网页授权获取用户基本信息 → **修改授权回调域名** (不带协议/端口/路径,如 pay.example.com)。
  2. 记录 **AppID (appid)**、**AppSecret (appsecret)**。
  3. 商户平台 → 产品中心 → 开发配置 → **支付授权目录** 设为支付发起页面域名 (如 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 → 继续统一下单

步骤 1:构造授权链接 (前端跳转或后端 302)


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**。

步骤 2:回调处理 — code 换取 openid


@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);
        }
    }
}

常见错误码对照表

| errcode | errmsg | 原因 | 解决 | |---------|--------|------|------| | 40029 | invalid code | code 已用过/过期/伪造 | code 仅可用一次,5 分钟过期;重新发起授权 | | 40003 | invalid openid | 用户未关注公众号 | 引导用户关注,或用小程序登录 wx.login | | 40163 | code been used | code 重复使用 | 确保回调幂等,code 用完即失效 | | 48001 | api unauthorized | 公众号无网页授权权限 | 认证公众号/服务号,配置授权回调域名 | | 40001 | invalid credential | appid/appsecret 错误 | 核对公众号后台开发配置 | | 40013 | invalid appid | appid 不存在 | 核对公众号/小程序 AppID |

安全与最佳实践

  1. state 防 CSRF:生成随机 UUID,存 Redis (key=state, value=orderId, TTL=10min),回调校验并删除。
  2. code 仅用一次:回调处理后立即使 code 失效 (微信侧自动),防重放。
  3. AppSecret 不前端暴露:code 换取 access_token **必须在后端**,切勿前端直连微信接口。
  4. access_token 缓存:有效期 2 小时,Redis 缓存 key=wx_access_token:{appid},提前 5 分钟刷新。
  5. refresh_token 延长有效期:30 天,用于静默刷新 access_token,避免用户再次授权。
  6. UnionID 体系:同一开放平台下多应用 (公众号/小程序/APP) 共享 UnionID,打通用户身份。

小程序/APP 场景差异

场景获取 openid 方式关键接口
公众号网页 (H5)OAuth2.0 网页授权/sns/oauth2/authorize → /sns/oauth2/access_token
小程序wx.login → code → 后端 jscode2sessionauth.code2Session (AppID+AppSecret+js_code)
APP (原生)微信开放平台授权登录/sns/oauth2/access_token (同网页,但回调 Scheme)

总结

获取 OpenID 是微信支付的**入口关卡**。掌握 **snsapi_base 静默授权**、**code 单次使用**、**state 防 CSRF**、**后端代理换取**、**错误码精准处理** 五大核心要点,配合 Redis 缓存 access_token/refresh_token,即可构建高可用、安全的授权基础设施。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved