【Vue】纯前端实现微信 H5 鉴权

最近在做微信网页版开发,整理出微信 H5 鉴权常见坑位,流程图分析如下,后附简易代码实现
H5鉴权流程图

1. 获取code

逻辑实现:通过重定向授权从地址栏拿取 code 并缓存,不可重复调用,不然会报 code used
官方传送门 => 微信开放文档-网页授权

async getAccessToken(redirectUri) {
   const code  = $$.getQueryValue('code', redirectUri) || $$.getStore('code')

   if (code) {
     const $ticketToken = await $$.setStoreAsync('ticketToken', this.getTicketToken)
     $$.setStore('code', code, true).setStoreAsync('token', this.getAuthToken.bind(this, code))

     return Promise.resolve(this.getConfigInfo($ticketToken, redirectUri))
   } else {
     const url = ('https://open.weixin.qq.com/connect/oauth2/authorize?' + $$.serializeParams({
       appid,
       redirect_uri: encodeURIComponent(redirectUri),
       response_type: 'code',
       scope: 'snsapi_userinfo',
       state: '200'
     }) + '#wechat_redirect')

     return redirect(url)
   }
 }

2. 获取 ticketToken

逻辑实现:调用https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET, 获取 access_token 并缓存,实现signature 签名

官方传送门 => 微信开放文档-获取access_token

async getTicketToken() {
  const { access_token } = await get(prefix + '/cgi-bin/token', {
     appid,
     secret,
     grant_type: 'client_credential'
   })

   return Promise.resolve(access_token)
 },
 async getTicket(ticketToken) {
   const { ticket, errcode } = await get(prefix + '/cgi-bin/ticket/getticket', {
     access_token: ticketToken,
     type: 'jsapi'
   })

   return Promise.resolve(ticket)
 }

3. 签名实现

逻辑实现:主要参数,第一步的回调 url ,第二步的 ticket,并生成指定字段,返回配置信息给 wx.config

官方传送门 => JS-SDK使用权限签名算法

async getConfigInfo(ticketToken, redirectUri) {
  const ticket = await $$.setStoreAsync('jsapiTicket', this.getTicket.bind(this, ticketToken))

  const req = {
    noncestr: $$.randomStr(),
    'jsapi_ticket': ticket,
    timestamp: Date.parse(new Date()),
    url: redirectUri
  }
  
  const signature = [$$.objSortByASCII, $$.serializeParams, $$.cryptoBase().sha1].reduce(function(target, callBack) {
    return callBack(target)
  }, Object.assign({}, req))

  return Promise.resolve({
    signature,
    nonceStr: req.noncestr,
    timestamp: req.timestamp,
    appId: appid
  })
}

4. 小结

setStoreAsync 首次请求,调用接口,之后返回缓存信息,采用 store2 进行持久化存储方案

async setStoreAsync(key, callBack) {
  let data = {}
  if (!this.store.get(key)) {
    data = await callBack()
    this.store.set(key, data)
  }

  return Promise.resolve(this.store.get(key))
}

5. 完整代码

以下代码经过封装,仅供参考,如有意向了解更多,欢迎评论分享 : -)


import $$ from '~/utils/' // 常用工具类实现

const prefix = '/api-wx'  // 固定请求头,本地代理实现跨域
const appid  =  ''  
const secret = ''

export default (axios, post, get, store, redirect) => {
  return {
    async getAccessToken(redirectUri) {
      const code  = $$.getQueryValue('code', redirectUri) || $$.getStore('code')

      if (code) {
        const $ticketToken = await $$.setStoreAsync('ticketToken', this.getTicketToken)
        $$.setStore('code', code, true).setStoreAsync('token', this.getAuthToken.bind(this, code))

        return Promise.resolve(this.getConfigInfo($ticketToken, redirectUri))
      } else {
        const url = ('https://open.weixin.qq.com/connect/oauth2/authorize?' + $$.serializeParams({
          appid,
          redirect_uri: encodeURIComponent(redirectUri),
          response_type: 'code',
          scope: 'snsapi_userinfo',
          state: '200'
        }) + '#wechat_redirect')

        return redirect(url)
      }
    },
    async getAuthToken(code) {
      const token = await get(prefix + '/sns/oauth2/access_token', {
        appid,
        secret,
        code,
        grant_type: 'authorization_code'
      })

      return Promise.resolve(token)
    },
    async refreshToken() {
      const token = await get(prefix + '/sns/oauth2/refresh_token', {
        appid,
        grant_type: 'refresh_token',
        refresh_token: $$.getStore('token')['refresh_token']
      })

      return Promise.resolve($$.setStore({ token }))
    },
    async getTicketToken() {
      const { access_token } = await get(prefix + '/cgi-bin/token', {
        appid,
        secret,
        grant_type: 'client_credential'
      })

      return Promise.resolve(access_token)
    },
    async getTicket(ticketToken) {
      const { ticket, errcode } = await get(prefix + '/cgi-bin/ticket/getticket', {
        access_token: ticketToken,
        type: 'jsapi'
      })

      return Promise.resolve(ticket)
    },
    async getConfigInfo(ticketToken, redirectUri) {
      const ticket = await $$.setStoreAsync('jsapiTicket', this.getTicket.bind(this, ticketToken))

      const req = {
        noncestr: $$.randomStr(),
        'jsapi_ticket': ticket,
        timestamp: Date.parse(new Date()),
        url: redirectUri
      }
      
      const signature = [$$.objSortByASCII, $$.serializeParams, $$.cryptoBase().sha1].reduce(function(target, callBack) {
        return callBack(target)
      }, Object.assign({}, req))

      return Promise.resolve({
        signature,
        nonceStr: req.noncestr,
        timestamp: req.timestamp,
        appId: appid
      })
    }
  }
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页