Nodejs实现微信网页授权登录

Nodejs实现微信网页授权登录

文章介绍了Nodejs实现微信网页授权登录,涉及原理、准备工作、具体步骤及安全优化等内容,基于OAuth 2.0协议,可引导用户授权并获取信息。

 次点击
23 分钟阅读

在如今移动互联网的浪潮中,微信已成为人们生活中不可或缺的社交工具。对于开发者而言,借助微信强大的用户基础,实现微信网页授权登录功能,能有效提升应用的用户体验和用户获取效率。

微信网页授权登录原理

微信网页授权登录基于 OAuth 2.0 协议,整个流程大致分为以下几个步骤:​

  • 引导用户授权:应用将用户引导至微信授权页面,用户在该页面确认是否授权应用获取其信息。​

  • 获取授权码:若用户同意授权,微信会回调应用事先配置好的重定向 URL,并附带一个临时的授权码(code)。​

  • 换取用户信息:应用使用获取到的授权码,通过向微信服务器发送请求,换取用户的 access_token 和 openid。​

  • 拉取用户信息:凭借 access_token 和 openid,应用可以进一步获取用户的详细信息,如头像、昵称等。

前端授权流程

利用这个流程,我们可以实现一个扫码登录的功能

准备工作

微信公众平台测试号为开发者提供了一个便捷的测试环境,无需复杂的公众号资质申请流程,通过简单注册即可获取测试号,拥有和正式公众号类似的接口权限,非常适合用于开发初期的功能调试与验证,包括网页授权登录功能的实现。

微信公众平台测试号配置

  • 访问微信公众平台测试号 申请页面 ,使用个人微信号扫码登录,即可获取一个测试号。​

  • 在测试号管理页面,记录下 AppIDAppSecret ,这两个参数在后续代码中会频繁使用。​

  • 往下滑动网页,在体验接口权限表中找到 网页服务 -> 网页帐号 -> 网页授权获取用户基本信息

  • 点击 修改 ,填入授权回调页面域名: 127.0.0.1注意:此处只填写ip即可,不需要加上http或https

Nodejs项目环境搭建

  • 新建一个 Nodejs 项目目录,打开终端,执行npm init -y命令,快速生成package.json文件,用于管理项目依赖。​

  • 安装axios用于发送 HTTP 请求,执行npm install axios;若使用express框架搭建 Web 服务(也可选择其他框架或原生 Nodejs 实现),执行npm install express。

具体实现步骤

本文主要介绍基于 nvpress 实现微信授权登录的方案。

二维码生成

要扫码登陆,首先就要生成二维码。二维码本质上就是存储数据的图片介质,其中的数据可以是 URL 也可以是文本等。通过二维码扫码工具就能扫出其中的数据。比如扫描上面设计图中的二维码,你就可以得到一段文本。所以基于此原理,我们就可以把 URL 存储在二维码中,微信扫码之后,会自动跳转到该 URL。

上面还提到,微信可以通过授权 URL 获取到用户的 openid,而第三步需要该 openid,所以我们的 URL 需要是一个授权 URL。

在 node.js 中生成授权 URL 可以借助于 wechat-oauth 这个包。详细步骤如下:

const OAuth = require('wechat-oauth');
const uuid = require('uuid');
const client = new OAuth('wechat_appid', 'wechat_appsecret');

/**
 * 获取微信授权码
 * @param {*} data
 * @param {*} req
 * @param {*} res
 * @returns
 */
register_rest_route('wechat', 'get-code', {
  methods: 'get',
  callback(data, req, res) {
    const url = client.getAuthorizeURL('http://127.0.0.1', '123123', 'snsapi_userinfo')
    // 创建一个唯一的token,用于轮询
    const wechat_token = uuid.v1()
    return {
      url,
      token: wechat_token,
    }
  },
})

其中 redirectUrl 是网页授权回调域名(需要填写与测试号中配置的信息一致),scope 是网页授权的方式,有 snsapi_base 和 snsapi_userinfo。state 是我们自定义的一个参数,重定向后会带上该参数,所以一般可以用该参数来表示不同的业务。更详细的信息可以参考 微信网页授权

这样用户访问该 URL 之后就会被重定向到上面设置的 redirectUrl,并带上 code 参数。在第三步的时候,就可以根据 code 来获取用户的 openid。

然后由于还需要确定是哪个用户在进行登陆,即将微信和电脑浏览器对应起来,所以还需要一个唯一字符串来标识。即在 URL 中加上一个唯一 token,这样微信就能根据该 token 知道是哪一个客户端(浏览器,也就是用户)在进行登陆了。

所以我们可以生成一个 uuid 作为 token。生成 uuid 可以使用 uuid 这个包。然后我们可以将 uuid 作为 state 参数来生成授权 URL。就像下面这样:

const url = client.getAuthorizeURL('http://127.0.0.1', '985123a0-7e4f-11e7-9022-fb7190c856e4', 'snsapi_userinfo');
console.log(url);
// https://open.weixin.qq.com/connect/oauth2/authorize?appid=APP_ID&redirect_uri=http%3A%2F%2F127.0.0.1&response_type=code&scope=snsapi_userinfo&state=985123a0-7e4f-11e7-9022-fb7190c856e4#wechat_redirect

最后根据这个带有 token 的 URL 生成一个二维码。生成二维码的步骤可以放在前端也可以放在后端,方式有很多种。

同时还需要做的事情是,将生成二维码的 token 也返回给客户端,因为后面还会用到该 token。可以将其放在 cookie 里面,也可以放在隐藏表单。

扫码获取token

当用户用微信扫描登陆页面的二维码时,就会自动跳转到二维码对应的 URL 上。

用户同意授权之后,接下来微信浏览器还将继续跳转回调域名,并带上 code 参数,可能跳转之后的页面就是

http://127.0.0.1/?code=CODE&state=985123a0-7e4f-11e7-9022-fb7190c856e4。

于是我们就可以自己的后端通过 state 参数中取得 token。

获取用户信息

在之前我们生成的二维码链接中,回调域名是 127.0.0.1 ,所以我们在对应的路由界面中进行判断是否有 code 这一参数

onMounted(() => {
  const searchParams = new URLSearchParams(window.location.search)
  const code = searchParams.get('code')

  if (code) {
    wxLogin(code)
  }
})

同时我们需要实现的后端接口是根据 code 进行下面的步骤,最终获取到用户的信息。

/**
 * 获取用户信息,并更新用户信息
 * @param {*} data
 * @param {*} req
 * @param {*} res
 * @returns
 */
register_rest_route('wechat', 'get-user-info', {
  methods: 'post',
  callback: async (data, req, res) => {
    const { code } = data
    const { data: tokenData } = await axios.get('https://api.weixin.qq.com/sns/oauth2/access_token?appid=' + AppID + '&secret=' + AppSecret + '&code=' + code + '&grant_type=authorization_code')
    let access_token = tokenData.access_token
    let openid = tokenData.openid
    const { data: userinfo } = await axios.get('https://api.weixin.qq.com/sns/userinfo?access_token=' + access_token + '&openid=' + openid + '&lang=zh_CN')
    const user = get_user_by_openid(userinfo.openid)
    let nvnonce = ''
    // 用户未注册则根据微信返回的信息创建一个新用户
    if (!user) {
      const user_id = nv_insert_user({...})
      // 设置cookie
      nvnonce = nv_create_nonce(user_id)
    } else {
      // 用户已存在则直接设置cookie
      nvnonce = nv_create_nonce(user.id)
    }
    // 记住,那就1年,反正nonce都是24小时过期,除非一直使用才能续期。否则就是会话
    res.cookie(
      'nvnonce',
      nvnonce,
      apply_filters('nv_user_login_cookie_options', {
        path: '/',
        maxAge: 24 * 60 * 60 * 1000,
        sameSite: 'Lax',
      })
    )
    return userinfo
  },
})

轮询

轮询可以循环发送 HTTP 请求,也可以使用 Web Socket

当服务端发现用户已经扫码之后,就可以将登陆状态设置为已登陆,如设置 session,然后返回给客户端。客户端发现已登陆成功,则跳转到登陆后的页面。

这里我们以轮询发送http请求为例:

/**
 * 检查用户是否登录
 * @param {*} data
 * @param {*} req
 * @param {*} res
 * @returns
 */
register_rest_route('wechat', 'check-login', {
  methods: 'post',
  callback: async (data, req, res) => {
    const { token } = data
    // 根据token判断用户是否已经注册成功
    const user = get_user_by_token(token)
    if (user) {
      // 注册成功后的操作,可以在此处创建nvnonce,同时前端调用/nv/check-nonce去检查用户信息
      return user
    } else {
      return {
        code: 0,
        msg: '用户不存在',
      }
    }
  },
})

改进

上面的设计方案存在的主要题是,二维码是一直有效的。如果考虑到二维码失效怎么处理?这个时候就可以简单改变一下思路。

前面是扫码的时候,将 token 和 openid 存储到数据库(或别的存储),客户端根据 token 轮询判断是否有数据。

考虑到二维码的实效性,则生成二维码的时候,就先将 token 存储到数据库,并设置一个token的 过期时间

当用户使用微信扫码的时候,获取到 token 和 openid。首先根据 token 判断一下数据库中是否有该 token 对应的数据,如果没有则不存储;如果有,则判断是否过期;如果有且 token 未过期,则更新 该 token 对应的 openid。这样就能达到二维码实效性的问题。

总结

其实文中大部分内容描述的都不是最优的解决方案。是因为最开始思考的不够,没想到那么全。当然,改进部分描述的可能也不是最好的方案。但如果没有之前想到的那些,可能更不会想到可以改进的地方,索性就这么在本文中记录了一下。

© 本文著作权归作者所有,未经许可不得转载使用。