TNW-授權(quán)獲取用戶信息
簡介
TNW: TypeScript(The) + Node.js(Next) + WeChat 微信公眾號開發(fā)腳手架,支持任何 Node.js 的服務(wù)端框架(Express、Nest、egg 等)
什么是 OAuth2.0
微信公眾平臺OAuth2.0授權(quán)詳細(xì)步驟
配置授權(quán)回調(diào)頁面域名
請參考之前寫的文章 微信公眾號開發(fā)之授權(quán)獲取用戶信息-Java版 此篇文章已有 2.4 w+ 的閱讀量
授權(quán)用戶信息的一些說明
關(guān)于網(wǎng)頁授權(quán)的兩種scope的區(qū)別說明
1、以 snsapi_base 為 scope 發(fā)起的網(wǎng)頁授權(quán),是用來獲取進(jìn)入頁面的用戶的 openid 的,并且是靜默授權(quán)并自動跳轉(zhuǎn)到回調(diào)頁的。用戶感知的就是直接進(jìn)入了回調(diào)頁(往往是業(yè)務(wù)頁面)
2、以 snsapi_userinfo 為 scope 發(fā)起的網(wǎng)頁授權(quán),是用來獲取用戶的基本信息的。但這種授權(quán)需要用戶手動同意,并且由于用戶同意過,所以無須關(guān)注,就可在授權(quán)后獲取該用戶的基本信息。
3、用戶管理 類接口中的 獲取用戶基本信息接口,是在用戶和公眾號產(chǎn)生消息交互或關(guān)注后事件推送后,才能根據(jù)用戶 openid 來獲取用戶基本信息。這個(gè)接口,包括其他微信接口,都是需要該用戶(即openid)關(guān)注了公眾號后,才能調(diào)用成功的。
關(guān)于特殊場景下的靜默授權(quán)
1、上面已經(jīng)提到,對于以snsapi_base為scope的網(wǎng)頁授權(quán),就靜默授權(quán)的,用戶無感知;
2、對于已關(guān)注公眾號的用戶,如果用戶從公眾號的會話或者自定義菜單進(jìn)入本公眾號的網(wǎng)頁授權(quán)頁,即使是scope為snsapi_userinfo,也是靜默授權(quán),用戶無感知。
具體而言,網(wǎng)頁授權(quán)流程分為四步:
1、引導(dǎo)用戶進(jìn)入授權(quán)頁面同意授權(quán),獲取code
2、通過 code 換取網(wǎng)頁授權(quán) access_token(與基礎(chǔ)支持中的access_token不同)
3、如果需要,開發(fā)者可以刷新網(wǎng)頁授權(quán) access_token,避免過期
4、通過網(wǎng)頁授權(quán) access_token 和 openid 獲取用戶基本信息(支持UnionID機(jī)制)
用戶同意授權(quán),獲取code
引導(dǎo)關(guān)注者打開如下授權(quán)的頁面URL:
若提示“該鏈接無法訪問”,請檢查參數(shù)是否填寫錯(cuò)誤,是否擁有scope參數(shù)對應(yīng)的授權(quán)作用域權(quán)限。
尤其注意:
- 由于授權(quán)操作安全等級較高,所以在發(fā)起授權(quán)請求時(shí),微信會對授權(quán)鏈接做正則強(qiáng)匹配校驗(yàn),如果鏈接的參數(shù)順序不對,授權(quán)頁面將無法正常訪問
- 跳轉(zhuǎn)回調(diào)redirect_uri,應(yīng)當(dāng)使用https鏈接來確保授權(quán)code的安全性且必須有在MP配置回調(diào)頁面的域名。
用戶同意授權(quán)后
如果用戶同意授權(quán),頁面將跳轉(zhuǎn)至 redirect_uri/?code=CODE&state=STATE。
特別注意 : code作為換取 access_token 的票據(jù),每次用戶授權(quán)帶上的 code 將不一樣,code 只能使用一次,5分鐘未被使用自動過期。
通過code換取網(wǎng)頁授權(quán)access_token
刷新access_token(如果需要)
拉取用戶信息(需scope為 snsapi_userinfo)
檢驗(yàn)授權(quán)憑證(access_token)是否有效
TNW 中的封裝
export class SnsAccessTokenApi {
private static authorizeUrl: string = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s";
private static accessTokenUrl: string = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"
private static refreshTokenUrl: string = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"
private static userInfoUrl: string = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s";
private static checkTokenUrl: string = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s";
/**
* 獲取授權(quán)鏈接
* @param redirectUri 回調(diào)地址
* @param scope
* @param state
*/
public static getAuthorizeUrl(redirectUri: string, scope: ScopeEnum, state?: string): string {
let url = util.format(this.authorizeUrl, ApiConfigKit.getApiConfig.getAppId, urlencode(redirectUri), scope);
if (state) {
url = url + "&state=" + state;
}
return url + "#wechat_redirect";
}
/**
* 通過code換取網(wǎng)頁授權(quán)access_token
* @param code
*/
public static async getSnsAccessToken(code: string) {
let url = util.format(this.accessTokenUrl, ApiConfigKit.getApiConfig.getAppId,
ApiConfigKit.getApiConfig.getAppScrect, code);
return HttpKit.getHttpDelegate.httpGet(url);
}
/**
* 刷新access_token
* @param refreshToken
*/
public static async refreshAccessToken(refreshToken: string) {
let url = util.format(this.refreshTokenUrl, ApiConfigKit.getApiConfig.getAppId, refreshToken);
return HttpKit.getHttpDelegate.httpGet(url);
}
/**
* 檢驗(yàn)授權(quán)憑證(access_token)是否有效
* @param accessToken 通過code換取的access_token
* @param openId
*/
public static async checkAccessToken(accessToken: string, openId: string) {
let url = util.format(this.checkTokenUrl, accessToken, openId);
return HttpKit.getHttpDelegate.httpGet(url);
}
/**
* 拉取用戶信息(需scope為 snsapi_userinfo)
* @param accessToken
* @param openId
* @param lang
*/
public static async getUserInfo(accessToken: string, openId: string, lang: Lang) {
let url = util.format(this.userInfoUrl, accessToken, openId, lang);
return HttpKit.getHttpDelegate.httpGet(url);
}
}
export enum ScopeEnum {
SNSAPI_BASE = "snsapi_base",
SNSAPI_USERINFO = "snsapi_userinfo"
}
export enum Lang {
ZH_CN = "zh_CN",
ZH_TW = "zh_TW",
EN = "en"
}
TNW 案例
訪問:http/https://域名/toAuth
回調(diào):http/https://域名/auth
app.get('/toAuth', (req, res) => {
let url = SnsAccessTokenApi.getAuthorizeUrl("http://xxx/auth", ScopeEnum.SNSAPI_USERINFO, "IJPay");
console.log("授權(quán)URL:", url);
res.redirect(url);
});
// 授權(quán)回調(diào)
app.get('/auth', (req, res) => {
let code = req.query.code;
let state = req.query.state;
console.log("code:", code, " state:", state);
SnsAccessTokenApi.getSnsAccessToken(code).then(data => {
let temp = JSON.parse(data.toString());
// 判斷 access_token 是否獲取成功
if (temp.errcode) {
// access_token 獲取失敗
res.send(temp);
return;
}
let access_token = temp.access_token;
let openid = temp.openid;
let scope = temp.scope;
if (scope == ScopeEnum.SNSAPI_USERINFO) {
// 獲取用戶信息
SnsAccessTokenApi.getUserInfo(access_token, openid, Lang.ZH_CN).then(data => {
res.send(data);
});
} else {
res.send(temp);
}
})
});
常見錯(cuò)誤
1、請?jiān)谖⑿趴蛻舳酥写蜷_
授權(quán)獲取用戶信息必須在微信客戶端中打開或者使用微信提供的 微信開發(fā)者工具
2、redirect_url 參數(shù)錯(cuò)誤
請檢查appId對應(yīng)的公眾平臺中設(shè)置的授權(quán)域名是否與你項(xiàng)目中配置的域名保持一致
3、測試號測試時(shí)提示未關(guān)注測試號
測試號測試授權(quán)是必須先關(guān)注的測試的號,官方這做是為了安全。正式環(huán)境微信認(rèn)證的服務(wù)號是不用關(guān)注就可以獲取用戶的信息。
源碼地址
-
TNW微信公眾號開發(fā)腳手架:https://gitee.com/javen205/TNW -
IJPay讓支付觸手可及:https://gitee.com/javen205/IJPay