美文网首页
egg搭建微信授权

egg搭建微信授权

作者: RadishHuang | 来源:发表于2020-06-08 12:32 被阅读0次

微信授权采用的是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}` +
                   `&timestamp=${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;

调试注意事项

测试账号
  • Token这个值是自己设置的,用于上面URL的合法校验。这边填写的地址是/wechat/check。会校验上面路由的check方法。

  • 要测试授权需要,接口配置信息先把域名通过,然后在底下的文档有一个网页授权获取用户信息的,也需要配置域名。才会回调服务器的接口。


    需要和上面的域名一致
修改后的域名

代码解析

  • 代码核心都是和前面写的授权流程一样。
  • 首先通过ngrok将本地的服务映射到外网的域名上。
  • 其次先把接口的域名信息配置好,通过/wechat/check方法,将微信传来的值进行sha1的加密,验证signature是否一致,最终把echostr返回给微信后台,则可以验证通过。
  • 接下来就是浏览器上访问域名/wechat/user/redirect,这个方法会根据不同参数拼接出授权的一跳地址,后台直接重定向到授权页面。
  • 当用户做了相关操作,如果是同意,回调的接口/wechat/user/oauth可以拿到一个code
  • 根据回调的code,请求微信的接口可以换取到access_tokenopenid
  • 根据access_tokenopenid可以解析到用户的信息,头像昵称位置。

相关文章

网友评论

      本文标题:egg搭建微信授权

      本文链接:https://www.haomeiwen.com/subject/ttrnzhtx.html