Implementation of JS-SDK Backend Interface for Microsoft Based on Node.js

Keywords: Javascript SHA1 JSON axios

I made a website, put it online, open it with WeChat and click Share, but after sharing, the link card to a friend comes with WeChat by default, as follows:



This title, description and picture comes with it by default. Ugly, sharing with others thought it was a stolen website. When you join WeChat's JSSDK, sharing can be customized as follows:



I admit that although the title and content of this sharing is not decent, it does not prevent me from expressing that we can define the content to be shared through WeChat JSSDK, and that we will step by step implement JSSDK's access from back-end Node.js from zero.

Become Test Public Number Developer

Logon Test Public Number Background

First we need to WeChat Public Platform Request Test Interface, Address: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
After logging in using WeChat scanning, you can test the account system on the WeChat public platform.

Become Test Public Number Developer

Secondly, in the WeChat public platform test account, scan the test number 2D, become the developer of the test public number

Interface Configuration Information

Modify interface configuration information

  1. The URL address must be the address on your server, which can be accessed through the browser's address bar (no server?That's okay. We'll build one later)
    Suppose the server address I filled in here is " http://www.your_server_name.com/wxJssdk"
  2. Token can be filled out as you like and used to generate signatures. (Don't know signatures?That's okay. I'll use it later)
    Suppose the Token I filled in here is " jegfjaeghfuccawegfgjdbh"

Clicking Submit at this time will prompt the configuration to fail, because at the time of submission, WeChat will request your server address, and your current configured address is not accessible, so it will prompt the configuration to fail.But don't worry, let's set up a simple Node server that WeChat can access.

Set up a simple Node server

We need to set up a server on the domain name http://www.your_server_name.com and expose an interface as/wxJssdk

const express = require('express')
const app = express()

app.get('/wxJssdk', (req, res) => {
  res.send('The request succeeded')
})

app.listen(80, err => {
  if(!err) console.log('connect succeed')
})

Now we'll go to http://www.your_server_name.com/wxJssdk in the address bar. If the page says "The request succeeded", we'll move on to the next step. If it doesn't succeed, check to see if your server starts a Node server, such as node index.js

Save the interface configuration information of the WeChat test public number background at this time, and still prompt that the configuration failed because we did not return as it requested.

Return corresponding content based on WeChat test public number request information

according to WeChat Public Number Development Document Access Guide When WeChat requests our configured interface, it will bring the following information

parameter describe
signature The WeChat cryptographic signature, signature combines the token parameter filled out by the developer with the timestamp parameter and the nonce parameter in the request.
timestamp time stamp
nonce random number
echostr Random String

The WeChat server will request the interface we configured with the information from the above tables through GET requests, and we must verify the information sent by WeChat according to the following requirements to ensure that it is the information sent by WeChat with the following verification process:

1) Dictionary ordering of three parameters: token, timestamp and nonce
2) sha1 encryption by splicing three parameter strings into one string
3) Developers can compare encrypted strings with signature s to identify that the request originated from WeChat

const express = require('express')
const app = express()
const sha1 = require('sha1')

app.get('/wxJssdk', (req, res) => {
  let wx = req.query

  let token = 'jegfjaeghfuccawegfgjdbh'
  let timestamp = wx.timestamp
  let nonce = wx.nonce

  // 1) Dictionary ordering of three parameters: token, timestamp and nonce
  let list = [token, timestamp, nonce].sort()

  // 2) sha1 encryption by splicing three parameter strings into one string
  let str = list.join('')
  let result = sha1(str)

  // 3) Developers can compare encrypted strings with signature s to identify that the request originated from WeChat
  if (result === wx.signature) {
    res.send(wx.echostr) // Returning echostr from WeChat indicates that the check was successful and no other one can be returned here
  } else {
    res.send(false)
  }
})

At this point, we restart the Node server and save the interface configuration information again to configure successfully.

Usage steps of WeChat JSSDK

according to WeChat JSSDK Description Document We need to do the following:

Fill in Secure Domain Name

Login to the WeChat public platform, enter the "Public Number Settings" and fill in the "JS Interface Security Domain Name", that is, the domain name of the interface to be invoked, does not contain the protocol

Front End Introducing JS

Introduce this JS file on a page where you need to call the JS interface, (supports https): http://res.wx.qq.com/open/js/jweixin-1.2.0.js

Fill in the interface configuration information

wx.config({
  debug: true, // Turn on debugging mode, and the return values of all APIs invoked will be displayed in the client alert. To view the incoming parameters, you can open them on the pc side, and the parameter information will be printed through the log, only on the pc side.
  appId: '', // Required, unique identification of public number
  timestamp: , // Required, time stamp for signature generation
  nonceStr: '', // Required, generate a random string of signatures
  signature: '',// Required, Signature
  jsApiList: [] // Required, list of JS interfaces to use, all JS interfaces listed in Appendix 2
});

Call Interface

Do what your front end should do, call the WeChat sharing interface, or other interfaces provided by WeChat, whatever you need. Of course, this is not the focus of our discussion. Let's see where the configuration information of WeChat comes from.

Configuration information needed to generate jssdk in Node Server

As you can see from the previous section, calling WeChat JSSDK requires the following information

  1. appId
  2. timestamp
  3. nonceStr
  4. signature
  5. jsApiList

Where:

  1. The first appId is the appId that tests the Public Number background, as we know
  2. The second timestamp can also be generated by ourselves
  3. The third nonceStr is optional and you can interpret it as a key
  4. The fourth signature requires us to generate it as required
  5. Item 5 is the interface name of the required interface

Generate signature

You must know about jsapi_ticket, which is a temporary ticket used by the public number to call the WeChat JS interface, before generating a signature.Normally, jsapi_ticket has a validity period of 7200 seconds, which is obtained by access_token.Because of the limited number of api calls to obtain jsapi_ticket, frequent refreshes of jsapi_ticket can result in limited api calls and affect their business, developers must cache jsapi_ticket globally in their own services.

To ensure that our appid, appsecret, nonceStr and other information is not exposed in the front end, the following steps will be taken on the server to prevent others from stealing information from obtaining (Note: WeChat requests have a daily limit, once exceeded, they can not be used, specific requests are limited to WeChat public number background viewable)

Generate access_token

According to the WeChat development document [Get access_token documentation], we need to request token from https://api.weixin.qq.com/cgi-bin/token by GET for appid and appsecret in the Wechat test public number background. Grant_type=client_credential&appid=APPID&secret=APPSECRET. After successful request, we will get the string that returns JSON transformation.

{"access_token":"ACCESS_TOKEN","expires_in":7200}

The specific request code is as follows:

const request = require('request')

const grant_type = 'client_credential'
const appid = 'your app id'
const secret = 'your app secret'

request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => {
  let access_toekn = JSON.parse(body).access_token
})

Get jsapi_ticket

const request = require('request')

const grant_type = 'client_credential'
const appid = 'your app id'
const secret = 'your app secret'

request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => {
  let access_toekn = JSON.parse(body).access_token

  request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => {
     let jsapi_ticket = JSON.parse(body).ticket
  })
})

Generate Signature

The steps for generating signatures are consistent with the initial algorithm for/wxJssdk as follows:

let jsapi_ticket = jsapi_ticket  // The jsapi_ticket obtained from the previous step
let nonce_str = '123456'    // Key, any string, can be randomly generated
let timestamp = new Date().getTime()  // time stamp
let url = req.query.url   // url link using interface, does not contain content after #

// Request the above strings, sorted by dictionary, then stitched by'&'as follows: where j > n > t > u, sorted manually directly here
let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '&timestamp=' + timestamp + '&url=' + url

// Encrypt with sha1
let signature = sha1(str)

The connected code is:

const request = require('request')

const grant_type = 'client_credential'
const appid = 'your app id'
const secret = 'your app secret'

request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => {
  let access_toekn = JSON.parse(body).access_token

  request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => {
     let jsapi_ticket = JSON.parse(body).ticket
     let nonce_str = '123456'    // Key, any string, can be randomly generated
     let timestamp = new Date().getTime()  // time stamp
     let url = req.query.url   // url link using interface, does not contain content after #

     // Request the above strings, sorted by dictionary, then stitched by'&'as follows: where j > n > t > u, sorted manually directly here
     let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '&timestamp=' + timestamp + '&url=' + url

     // Encrypt with sha1
     let signature = sha1(str)
  })
})

Expose interface, return to front end

app.post('/wxJssdk/getJssdk', (req, res) => {
  const request = require('request')

  const grant_type = 'client_credential'
  const appid = 'your app id'
  const secret = 'your app secret'

  request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => {
    let access_toekn = JSON.parse(body).access_token

    request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => {
       let jsapi_ticket = JSON.parse(body).ticket
       let nonce_str = '123456'    // Key, any string, can be randomly generated
       let timestamp = new Date().getTime()  // time stamp
       let url = req.query.url   // url link using interface, does not contain content after #

       // Request the above strings, sorted by dictionary, then stitched by'&'as follows: where j > n > t > u, sorted manually directly here
       let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '&timestamp=' + timestamp + '&url=' + url

       // Encrypt with sha1
       let signature = sha1(str)

       res.send({
         appId: appid,
         timestamp: timpstamp,
         nonceStr: nonce_str,
         signature: signature,
       })
    })
  })
})

Front-end requests back-end interfaces for configuration information

Get Configuration

axios.post('/wxJssdk/getJssdk', {url: location.href}).then((response) => {
  var data = response.data

  wx.config({
    debug: false, // Turn on debugging mode, and the return values of all APIs invoked will be displayed in the client alert. To view the incoming parameters, you can open them on the pc side, and the parameter information will be printed through the log, only on the pc side.
    appId: data.appId, // Required, unique identification of public number
    timestamp: data.timestamp, // Required, time stamp for signature generation
    nonceStr: data.nonceStr, // Required, generate a random string of signatures
    signature: data.signature,// Required, signed, see Appendix 1
    jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // Required, list of JS interfaces to use, all JS interfaces listed in Appendix 2
  });

})

Do what you want, for example, customize sharing

if (wx) {
  axios.post('/wxJssdk/getJssdk', {url: location.href}).then((response) => {
    var data = response.data

    wx.config({
      debug: false, // Turn on debugging mode, and the return values of all APIs invoked will be displayed in the client alert. To view the incoming parameters, you can open them on the pc side, and the parameter information will be printed through the log, only on the pc side.
      appId: data.appId, // Required, unique identification of public number
      timestamp: data.timestamp, // Required, time stamp for signature generation
      nonceStr: data.nonceStr, // Required, generate a random string of signatures
      signature: data.signature,// Required, signed, see Appendix 1
      jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // Required, list of JS interfaces to use, all JS interfaces listed in Appendix 2
    });

    wx.ready(function () {
      wx.onMenuShareTimeline({
      title: wxShare.title,
      desc: wxShare.desc,
      link: wxShare.link,
      imgUrl: wxShare.imgUrl
      });

      wx.onMenuShareAppMessage({
      title: wxShare.title,
      desc: wxShare.desc,
      link: wxShare.link,
      imgUrl: wxShare.imgUrl
    });
  })

    wx.error(function (res) {
       // Failure to verify config information executes error functions, such as signature expiration that causes verification to fail. The specific error information can be viewed either by opening config's debug mode or by returning the res parameter, where the SPA can update the signature.
    })
  })

}

So far, the back-end is configured and we can use the WeChat interface properly, but WeChat's daily interface requests are limited to 2000 times per day, so if the website goes online, your interface will fail if you visit more than 2000 times a day, and request the WeChat interface twice each time, which wastes requesting time, so we need to slow down the retrieval of information above.There is a backend to avoid interface failure and multiple requests for WeChat backend.

Cache access_token and jsapi_ticket

Code directly here, caching using the node_cache package

const request = require('request')
const express = require('express')
const app = express()
const sha1 = require('sha1')
const waterfall = require('async/waterfall')
const NodeCache = require('node-cache')
const cache = new NodeCache({stdTTL: 3600, checkperiod: 3600}) //Expires after 3600 seconds

app.get('/wxJssdk', (req, res) => {
  let wx = req.query

  // 1) Dictionary ordering of three parameters: token, timestamp and nonce
  let token = 'jegfjaeghfuyawegfgjdbh'
  let timestamp = wx.timestamp
  let nonce = wx.nonce

  // 2) sha1 encryption by splicing three parameter strings into one string
  let list = [token, timestamp, nonce]
  let result = sha1(list.sort().join(''))

  // 3) Developers can compare encrypted strings with signature s to identify that the request originated from WeChat
  if (result === wx.signature) {
    res.send(wx.echostr)
  } else {
    res.send(false)
  }
})

app.get('/wxJssdk/getJssdk', (req, res) => {
  let grant_type = 'client_credential'
  let appid = 'your app id'
  let secret = 'your app secret' // appscret

  let steps = []

  // First step, get access_token
  steps.push((cb) => {

  let steps1 = []

    // Step 1.1, read access_token from the cache
    steps1.push((cb1) => {
      let access_token = cache.get('access_token', (err, access_token) => {
        cb1(err, access_token)
      })
    })

    // Step 1.2, if there is access_token in the cache, returns directly, if not, reads access_token from the server
    steps1.push((access_token, cb1) => {
      if (access_token) {
        cb1(null, access_token, 'from_cache')
      } else {
        request('https://api.weixin.qq.com/cgi-bin/token?grant_type=' + grant_type + '&appid=' + appid + '&secret=' + secret, (err, response, body) => {
          cb1(err, JSON.parse(body).access_token, 'from_server')
        })
      }
    })

    // Step 1.3, Cache access_token if it is newly fetched from the server, otherwise return directly
    steps1.push((access_token, from_where, cb1) => {
      if (from_where === 'from_cache') {
        console.log(' === Successfully read from cache access_token: ' + access_token + ' ===')
        cb1(null, access_token)
      } else if (from_where === 'from_server') {
        cache.set('access_token', access_token, (err, success) => {
          if (!err && success) {
            console.log(' === Cache expired, read from server access_token: ' + access_token + ' ===')
            cb1(null, access_token)
          } else {
            cb1(err || 'cache Set up access_token An unknown error occurred')
          }
        })
      } else {
        cb1('1.3 Obtain from_where When, from_where Value is empty')
      }
    })



    waterfall(steps1, (err, access_token) => {
      cb(err, access_token)
    })
  })


  // Step 2, get a ticket
  steps.push((access_token, cb) => {
    let steps1 = []

    // Step 2.1, Read ticket s from the cache
    steps1.push((cb1) => {
      let ticket = cache.get('ticket', (err, ticket) => {
        cb1(err, ticket)
      })
    })

    // Step 2.2, tickets in the cache are returned directly, if not, tickets are read from the server
    steps1.push((ticket, cb1) => {
      if (ticket) {
        cb1(null, ticket, 'from_cache')
      } else {
        request('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi', (err, response, body) => {
          cb1(err, JSON.parse(body).ticket, 'from_server')
        })
      }
    })

    // Step 2.3, Cache the new ticket fetched from the server, otherwise return directly
    steps1.push((ticket, from_where, cb1) => {
      if (from_where === 'from_cache') {
        console.log(' === Successfully read from cache ticket: ' + ticket + ' ===')
        cb1(null, ticket)
      } else if (from_where === 'from_server') {
        cache.set('ticket', ticket, (err, success) => {
          if (!err && success) {
            console.log(' === Cache expired, read from server ticket: ' + ticket + ' ===');
            cb1(null, ticket)
          } else {
            cb1(err || 'cache Set up ticket An unknown error occurred')
          }
        })
      } else {
        cb1('2.3 Obtain from_where When, from_where Value is empty')
      }
    })

    waterfall(steps1, (err, ticket) => {
      cb(err, ticket)
    })
  })


  // Step 3, Generate Signatures
  steps.push((ticket, cb) => {
    let jsapi_ticket = ticket
    let nonce_str = '123456'
    let timestamp = new Date().getTime()
    let url = req.query.url

    let str = 'jsapi_ticket=' + jsapi_ticket + '&noncestr=' + nonce_str + '&timestamp=' + timestamp + '&url=' + url
    let signature = sha1(str)

    cb(null, {
      appId: appid,
      timestamp: timestamp,
      nonceStr: nonce_str,
      signature: signature,
      ticket: ticket
    })
  })

  waterfall(steps, (err, data) => {
    if (err) {
      res.send({status: 'error', data: err})
    } else {
      res.send({status: 'success', data: data})
    }
  })
})

app.use('/wxJssdk/public', express.static('public'))

app.listen(80, err => {
  if(!err) console.log('connect succeed')
})

Posted by MikeL7 on Thu, 13 Jun 2019 09:43:22 -0700