微信授权采用的是oauth2.0的授权机制,这个与微博,支付宝等授权机制都是通用的。授权会产生
access_token
这个票据只是为了做授权的认证票据。可以根据这个票据换取用户的详细信息。微信授权机制有一个刷新机制,这个暂时可以不用管,现在基本上都是每次都去授权,都去获取一次access_token
。值得注意的一点:订阅号不管认不认证,授权后都没办法拿到用户信息。只有认证过的服务号才有权限拿用户信息。
授权流程
-
访问服务器的获取收取地址接口,一般我们把这个称为重定向,拿到这个地址,服务器会重定向到授权页面,这就是所谓的一跳。如下图。
授权图 -
用户点击允许,微信回调我们配置的回调地址,这时候地址后面会带有
code
参数。这就是所谓的二跳。 -
二跳回来后根据
code
换取access_token
,根据这个票据就能换取到用户信息。
egg中代码实现
- 前端直接访问
https://xxx.com/receive
,该方法会获取是否有jwt
,如果有jwt
,则从定向到前端的主页面,否则的话,进行微信授权的跳转。 - 路由
router.js
的配置。
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
// 公众号校验
router.get('/wechat/check', controller.wechat.user.check);
// 用户授权
router.get('/receive', controller.user.receive.index);
// 重定向地址。一跳地址
router.get('/wechat/user/redirect', controller.wechat.user.redirect);
// 授权回调地址。二跳地址
router.get('/wechat/user/oauth', controller.wechat.user.oauth);
// 获取签名
router.get('/wechat/user/signa', controller.wechat.user.signa);
};
- 控制器
controller
的配置
'use strict';
const Controller = require('egg').Controller;
class UserController extends Controller {
// 微信配置接口域名的时候,需要有验证地方
async check() {
const echostr = this.ctx.service.wechat.user.check();
if (echostr) {
// 加密后的数据需要与签名保持一致,验证成功
this.ctx.body = echostr;
} else {
this.ctx.body = 'Fail';
}
}
// 重定向页面
async redirect() {
const url = this.ctx.service.wechat.user.fetchUrl();
// 重新定向到用户授权的微信页面
this.ctx.redirect(url);
}
// 授权成功回调接口
async oauth() {
const { ctx } = this;
const { code } = ctx.query;
if (!code) { ctx.failure(422, 'code missing'); }
const { access_token, openid } = await ctx.service.wechat.user.fechAccessToken(code);
if (!access_token || !openid) { ctx.failure(422, 'access_token openid missing'); }
const userInfo = await ctx.service.wechat.user.getUserInfo(access_token, openid);
const token = await ctx.service.wechat.user.saveInfo(userInfo.data);
// 第一种方式:token拼到h5的地址后面
// 授权成功后,将token拼接到地址给到前端
// const url = `${this.ctx.app.config.wechat.Domain}?token=${token}`;
// 第二种方式:存入session,从session去拿token。
ctx.session.token = token;
ctx.redirect(url);
}
// 获取微信签名,用于JSSDK的功能
async signa() {
const { ctx } = this;
ctx.validate({ url: 'string' });
// 先拿到token
const { token } = await ctx.service.wechat.token.fetchToken();
if (!token) { ctx.failure(422, 'assess_token missing') };
// 根据token换ticket
const { ticket } = await ctx.service.wechat.ticket.fetchTicket(token);
if (!ticket) { ctx.failure(422, 'ticket missing') };
const result = await ctx.service.wechat.user.signa(ticket);
ctx.success(result);
}
}
module.exports = UserController;
- 代码核心的实现
service
的代码
'use strict';
const crypto = require('crypto');
const BaseService = require('../base');
class UserService extends BaseService {
constructor(ctx) {
super(ctx);
this.model = ctx.model.Wechat.User;
}
// 签名方法,前端需要的签名字段有这四个 { appId, timestamp, nonceStr, signature }
async signa(ticket) {
const { ctx } = this;
const { url } = ctx.request.body;
// 随机字符串
// const noncestr = 'bl32uux2kzhesx7';
const noncestr = Math.random().toString(36).substr(2, 15);
// 时间戳
// const timestamp = '1521097369';
const timestamp = parseInt(new Date().getTime() / 1000, 10);
// 签名
const string = `jsapi_ticket=${ticket}` +
`&noncestr=${noncestr}` +
`×tamp=${timestamp}` +
`&url=${url}`;
const signature = crypto.createHash('sha1').update(string).digest('hex');
const appId = ctx.app.config.wechat.AppId;
return { timestamp, noncestr, signature, appId };
}
// ========== 授权需要的方法 ===============
// 微信配置接口域名的时候,需要有验证地方
check() {
const { ctx } = this;
const { signature, nonce, timestamp, echostr } = ctx.query;
const token = ctx.app.config.wechat.Token;
// 加密
const str = [token, timestamp, nonce].sort().join('');
const sha = crypto.createHash('sha1').update(str).digest('hex');
return sha === signature ? echostr : null;
}
// 拼接授权页面的参数和地址
fetchUrl() {
// 授权成功后,二跳重定向,GET调用的服务器接口。就是controller中的oauth方法
const target = this.ctx.app.config.wechat.Domain + 'wechat/user/oauth';
// 两种类型 snsapi_base 这个只能获取到openid
const scope = "snsapi_userinfo";
// const scope = "snsapi_base";
// 传递参数 /wechat-redirect?url=前端首页地址&b=前端可能需要的额外参数。 可以传递多个参数
const { url, b } = this.ctx.request.body;
const state = `${url}_${b}`;
return this.getAuthorizeUrl(scope, target, state);
}
// =============== 微信授权步骤 ============
// 第一步:用户同意授权,获取code,授权一跳页面,进入让用户点击授权的页面。授权完毕,二跳到自己服务器上的页面
// 默认 scope=snsapi_base 这个只能获取到openid
getAuthorizeUrl(scope = 'snsapi_base', target, state) {
const encodeTarget = encodeURIComponent(target);
return `https://open.weixin.qq.com/connect/oauth2/authorize?` +
`appid=${this.ctx.app.config.wechat.AppId}&` +
`redirect_uri=${encodeTarget}&` +
`response_type=code&`+
`scope=${scope}&`+
`state=${state}#wechat_redirect`
}
// 第二步:通过code换取网页授权access_token
async fechAccessToken(code) {
const url = `https://api.weixin.qq.com/sns/oauth2/access_token?` +
`appid=${this.ctx.app.config.wechat.AppId}&` +
`secret=${this.ctx.app.config.wechat.AppSecret}&` +
`code=${code}&` +
`grant_type=authorization_code`
const result = await this.ctx.curl(url, {
dataType: 'json',
timeout: 5000
});
return result.data;
}
// 第三步:拉取用户信息(需scope为 snsapi_userinfo)
async getUserInfo(token, openid, lang = 'zh_CN') {
const url = `https://api.weixin.qq.com/sns/userinfo?`+
`access_token=${token}&` +
`openid=${openid}&` +
`lang=${lang}`;
const result = await this.ctx.curl(url, {
dataType: 'json',
timeout: 5000,
});
return result;
}
}
module.exports = UserService;
调试注意事项
- 登录公众平台测试账号系统,会分配好
AppId
和AppSecret
-
Token
这个值是自己设置的,用于上面URL
的合法校验。这边填写的地址是/wechat/check
。会校验上面路由的check
方法。 -
要测试授权需要,接口配置信息先把域名通过,然后在底下的文档有一个网页授权获取用户信息的,也需要配置域名。才会回调服务器的接口。
需要和上面的域名一致
- 域名可以临时用
ngrok
将本地的地址映射到公网。具体的使用可以访问本人的ngrok使用方法
代码解析
- 代码核心都是和前面写的授权流程一样。
- 首先通过
ngrok
将本地的服务映射到外网的域名上。 - 其次先把接口的域名信息配置好,通过
/wechat/check
方法,将微信传来的值进行sha1
的加密,验证signature
是否一致,最终把echostr
返回给微信后台,则可以验证通过。 - 接下来就是浏览器上访问域名
/wechat/user/redirect
,这个方法会根据不同参数拼接出授权的一跳地址,后台直接重定向到授权页面。 - 当用户做了相关操作,如果是同意,回调的接口
/wechat/user/oauth
可以拿到一个code
。 - 根据回调的
code
,请求微信的接口可以换取到access_token
和openid
。 - 根据
access_token
和openid
可以解析到用户的信息,头像昵称位置。
网友评论