用戶點擊授權連結
↓
微信授權頁面 (用戶同意)
↓
重導向至 redirect_uri?code=CODE&state=STATE
↓
開發者後端用 code 換取 access_token + openid
↓
拿到 openid 進行業務邏輯
構造 URL 導向用戶:
https://open.weixin.qq.com/connect/oauth2/authorize?
appid=APPID
&redirect_uri=REDIRECT_URI
&response_type=code
&scope=SCOPE
&state=STATE
#wechat_redirect
| 參數 | 必填 | 說明 |
|---|---|---|
| appid | 是 | 公眾號/開放平台 AppID |
| redirect_uri | 是 | 授權後重導向地址,需 URL Encode,域名需在公眾號後台配置 |
| response_type | 是 | 固定為 code |
| scope | 是 | snsapi_base (靜默授權,僅 openid) / snsapi_userinfo (需用戶同意,可獲用戶資訊) |
| state | 否 | 防 CSRF 攻擊,重導向時原樣回傳,建議使用隨機字串 |
後端伺服器呼叫 (切勿在前端直接呼叫,洩露 Secret):
GET https://api.weixin.qq.com/sns/oauth2/access_token?
appid=APPID
&secret=SECRET
&code=CODE
&grant_type=authorization_code
成功回傳:
{
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"openid": "OPENID", // 核心目標
"scope": "SCOPE"
}
若 scope=snsapi_userinfo:
GET https://api.weixin.qq.com/sns/userinfo?
access_token=ACCESS_TOKEN
&openid=OPENID
&lang=zh_CN
public ActionResult WeChatLogin() {
string appId = "你的AppId";
string redirectUri = HttpUtility.UrlEncode("https://你的域名/WeChatCallback");
string scope = "snsapi_userinfo"; // 或 snsapi_base
string state = Guid.NewGuid().ToString("N");
Session["wx_state"] = state; // 儲存驗證用
string url = $"https://open.weixin.qq.com/connect/oauth2/authorize?" +
$"appid={appId}&redirect_uri={redirectUri}&response_type=code" +
$"&scope={scope}&state={state}#wechat_redirect";
return Redirect(url);
}
public async Task WeChatCallback(string code, string state) {
// 驗證 state 防 CSRF
if (state != Session["wx_state"]?.ToString()) {
return Content("非法請求");
}
string appId = "你的AppId";
string secret = "你的Secret";
string tokenUrl = $"https://api.weixin.qq.com/sns/oauth2/access_token?" +
$"appid={appId}&secret={secret}&code={code}&grant_type=authorization_code";
using (HttpClient client = new HttpClient()) {
string json = await client.GetStringAsync(tokenUrl);
dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
if (result.errcode != null) {
return Content($"微信錯誤: {result.errmsg}");
}
string openId = result.openid;
string accessToken = result.access_token;
// 這裡拿到 openId,進行業務邏輯
// 例如:查詢/建立本地用戶、生成 JWT Token、設定 Cookie...
return Redirect("/Home/Index"); // 攜帶用戶身份進入系統
}
}
// 安裝 NuGet: Senparc.Weixin.MP
var oauthApi = new OAuthApi();
var result = oauthApi.GetAccessToken(appId, secret, code);
if (result.errcode == ReturnCode.请求成功) {
string openId = result.openid;
// 後續業務...
}
| 風險 | 防護 |
|---|---|
| Secret 洩露 | 僅後端儲存,前端不直接呼叫 token API |
| CSRF 攻擊 | 使用 state 參數,驗證回呼時比對 |
| Code 重放 | Code 單次使用,10 分鐘過期,驗證後即失效 |
| OpenID 洩露 | 傳輸加密 (HTTPS)、不在 URL 直接暴露 |
redirect_uri 域名未授權:公眾號後台 → 開發配置 → 網頁授權域名 設定scope 參數錯誤:訂閱號僅支援 snsapi_base,服務號支援兩種code 已被使用:確保邏輯不重複呼叫 token APIaccess_token 過期:使用 refresh_token 刷新,或重新授權