微信公衆號開發——signature、access_token、jsapi_ticket的獲取

微信公衆號開發——signature、access_token、jsapi_ticket的獲取

微信公衆號開發中幾個階段的權限校驗,相對於前端同學來說(特別是沒有nodejs基礎的同學來說)可能相當費解,不過話說回來,現代前端不會nodejs可能會混的相當難受。

咱們今天把這些煩人的 access_token、 jsapi_ticket、signature 一次盤明白,本文所有操作都基於公衆號的測試號,因此在開始 bibi 前需要先申請一個公衆號的測試號,這裏不再贅述。

一、消息接口

稍微複雜一些的公衆號服務都會涉及自定義回覆,比如某些消息內容需要返回數據庫裏面的數據,這就需要用到微信提供的消息接口:

第一步:填寫接口配置信息

在這裏插入圖片描述

如上圖

注意!在提交時微信會向你填寫的URL地址發送一個get請求,所以提交之前你必須保證這個域名下的相應接口必須能正常返回(這裏就涉及到了下一步要說的微信校驗)

第二步:實現微信接口校驗和消息接口

其實按理說這個才應該是第一步,因爲如果服務端接口沒有實現,上一步根本不能成功。好吧,順序我就不改了,能看明白就行。

Step1:微信校驗接口實現

這裏是個什麼概念呢?就是在你提交上面第一步填寫的URL地址的時候,微信服務器會向你的地址發送一個get請求,同時帶上四個參數:
在這裏插入圖片描述
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/2020050414182280.png

爲了你的服務器安全,你需要校驗這個請求是否來自微信,校驗的方法就是:將你自己保存的token和微信帶過來的timestamp、nonce三個字段進行字典排序後使用sha1算法加密,把得到的結果與微信帶過來的signature進行對比,如果相等說明對方確實擁有你設定的token,然後把微信帶過來的echostr 隨機字符串原樣返回。

注意,這一步的作用是你的服務器校驗微信,如果你不想校驗,直接返回echostr也是可以配置成功的,下面來一段實現代碼

上代碼前先聲明一下使用的框架和工具:

  1. 服務node框架使用的koa
  2. 路由中間件使用了koa-router
  3. crypto提供hash算法
  4. 發送請求使用aixos
const crypto = require("crypto");
// 我把appid和 appsecret 放在了conf.js中
const conf = require("./conf");

router.get("/wechat1", async ctx => {
  console.log("微信校驗...", ctx.url);
  const { query } = url.parse(ctx.url, true);
  const { signature, nonce, timestamp, echostr } = query;
  const str = [conf.token, nonce, timestamp].sort().join("");
  const signed = crypto
    .createHash("sha1")
    .update(str)
    .digest("hex");

  console.log("接收到的簽名爲:", signature);
  console.log("計算得到簽名爲:", signed);

  if (signature === signed) {
    console.log("對比結果成功!!!");
    ctx.body = echostr;
  } else {
    console.log("對比結果不通過");
    ctx.body = "你不是微信";
  }
});

接下來是服務端代碼(省略了中間件的引入和一些常規代碼)

Step2:消息接口實現

這中間的原理就是,每當用戶向公衆號發送消息的時候,微信服務器會向你的服務器發送一個post請求,並攜帶以下內容:

  • ToUserName – 消息發送給誰
  • FromUserName – 消息來自哪裏
  • Content – 消息內容
  • MsgType – 消息類型

我們要做的就是根據Content 來返回自己預定的內容就好了,因爲默認情況下微信請求時通過xml格式來傳輸數據的,所以我使用了 xml2js 這個工具來實現 xml 和js 對象兩種格式的轉換。

const xml2js = require("xml2js");
// 這個wechat 接口是你自己可以在上面的接口配置信息裏面自己設定的
router.post("/wechat", async ctx => {
  const { xml: msg } = ctx.request.body;
  console.log("receive...", msg);
  const builder = new xml2js.Builder();
  //   微信需要接收xml格式數據,所以這裏使用xml2js轉譯成xml格式
  const result = builder.buildObject({
    xml: {
      ToUserName: msg.FromUserName,
      FromUserName: msg.ToUserName,
      CreateTime: Date.now(),
      MsgType: msg.MsgType,
      Content: "你好 " + msg.Content
    }
  });

  ctx.body = result;
});

以上就是消息接口的實現,來看個效果:
在這裏插入圖片描述

嗯,好吧,你纔好2


二、服務端 access_token

微信公衆號爲開發者提供了一些針對服務端的信息服務,比如 獲取用戶列表,獲取用戶基本信息,地理位置 等,但是呢,不能是臺服務器就隨便調用微信的接口(也不安全),因此就有了服務端的access_token,它是公衆號的全局唯一接口調用憑證。以下是官方的接口條用請求說明:
在這裏插入圖片描述

這個咱們直接按照官方文檔直接調用就好了,比較簡單,下面是代碼:

// 我把appid和 appsecret 放在了conf.js中
const conf = require("./conf.js");
const tokenCache = {
  access_token: "",
  update_time: Date.now(),
  expires_in: 7200
};

async function getToken() {
  const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${conf.appid}&secret=${conf.appsecret}`;
  let res = await axios.get(url);
  console.log("res: ", res.data);

  // 保存access_token
  Object.assign(tokenCache, res.data, { update_time: Date.now() });
};

當然,上面的代碼只是一個簡單的示例,所以我把獲取回來的token直接保存到內存中了,實際工作中你可以把它存到數據庫中。那麼獲取到token我們就可以大膽使用微信提供的信息服務了,下面示範一個獲取用戶信息:

async function getFollowers() {
  // 先獲取用戶列表
  const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${tokenCache.access_token}`;
  let res = await axios.get(url);
  console.log("res.data.openid", res.data);

  let openids = res.data.data.openid.map(item => ({
    openid: item,
    lang: "zh_CN"
  }));

  // 獲取用戶信息
  const url1 = `https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=${tokenCache.access_token}`;

  console.log("openids--->", openids);
  res = await axios.post(url1, { user_list: openids });

  console.log("followers: ", res.data);
  ctx.body = res.data;
}); 

下圖是獲取回來的結果信息:
在這裏插入圖片描述


三、網頁端access_token

如果用戶在微信上訪問第三方網頁,公衆號可以通過微信網頁授權機制,獲取用戶信息,有的同學可能會問,第二步不是已經獲取了一次 access_token 嗎?實際上兩個token的使用範圍是不一樣的,剛剛的服務端token可以獲取你的公衆號用戶列表 也就是關注了你公衆號的用戶,而這裏的網頁端token是可以獲取未關注用戶的用戶信息的

我們平常使用微信訪問第三方網頁的時候是不是也經常會跳轉到一個授權頁面呢?相信你一定有印象

這一步是微信校驗中最複雜的一步,使用的是oAuth2 第三方校驗方式,目前市面上的第三方登錄用的基本都是oAuth2認證,對oAuth2校驗不是很了了解的同學可以移步阮一峯老師的博客:傳送門 理解OAuth 2.0

因爲比較複雜咱們把這裏拆分爲三步:

第一步:重定向到微信的認證頁面、用戶授權後

下面是微信官方的說明:
image-20200504104201167.png

這裏有非常重要的一步是 拼接返回地址 這個返回地址就是當用統一授權後微信頁面會從定向到的地址,重定向的時候會在查詢參數裏面拼上用戶獲取access_token的code。

// 我把appid和 appsecret 放在了conf.js中
const conf = require("./conf.js");

router.get("/wxAuthorize", async ctx => {
  const state = ctx.query.id;
  const scope = "snsapi_userinfo";
  console.log("href: ", ctx.href);

  const path = new URL(ctx.href);
  const redirectUrl = `${path.protocol}//${path.hostname}/wxCallback`;

  const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${conf.appid}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`
  ctx.redirect(url);
});

第二步:用微信返回的code去請求access_token

上一步中提到,當用戶點擊確定按鈕同意授權後,微信會把頁面重定向到你預先定義的 redirectUrl 並附上獲取access_token 的 code,我們要做的就是定義好要跳轉的路由,在裏面拿到code,獲取access_token。爲了更好理解,這裏附上文檔截圖:
在這裏插入圖片描述

const conf = require("./conf.js");

router.get("/wxCallback", async ctx => {
  const code = ctx.query.code;
  console.log("wxCallback...");

  // 使用axios發送get請求
  const res = await axios.get('https://api.weixin.qq.com/sns/oauth2/access_token', {
    params: {
      appid: conf.appid,
      secret: conf.appsecret,
      code: code,
      grant_type: 'authorization_code'
    }
  });
  console.log("token_res: ", res);
  // 保存access_token 需要連接數據庫,我這裏省略了

  // 重定向回訪問頁面
  ctx.redirect("/);
});

第三步:拉取用戶信息

先來張參數截圖:
在這裏插入圖片描述
這裏就非常簡單了,直接axios發送請求獲取回來就ok,就不上示例代碼了。

當然上面省略了一步刷新access_token,這個我認爲比較簡單,而且跟獲取token差不多,就不贅述了。


四、JSSDK的授權和使用

這一步的目的當然就是 爲了能暢通無阻的調用微信提供的拍照、錄音、拍視頻等api 因爲微信規定,所有頁面在使用它提供的api之前必須先注入配置信息,並且跳轉到一個url不同的頁面都必須進行簽名。這裏讓前端同學費解的就是這個簽名了。

同樣,咱們分解成三步:

第一步:用服務端access_token獲取jsapi_ticket

在這裏插入圖片描述

在計算簽名之前必須先用 access_token 獲取 jsapi_ticket,注意!這裏的 access_token 是指我們在第二步提到的服務端 access_token。 先不上代碼,在第二步統一po上來

第二步:拼接字符串,計算簽名

在這裏插入圖片描述

這裏簽名需要noncestr、jsapi_ticket、timestamp、url 這四個參數,把這四個參數按照ASCII碼從小到大書序排序拼接後進行sha1算法即可得出簽名:

const crypto = require("crypto");
const conf = require("./conf");

router.get("/getJsConfig", async ctx => {
  // 第一步:獲取 jsapi_ticket
 	let res = await axios.get('https://api.weixin.qq.com/cgi-bin/ticket/getticket',{
    params: {
      access_token: tokenCache.access_token,
      type: 'jsapi'
    }
  });
  const jsapi_ticket = res.data.ticket;
  
  // 計算簽名
  const timestamp = Date.now();
  const url = ctx.query.url;
  const noncestr = Math.random().toString(36).substring(2);
  
  const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`;
  const signature = crypto.createHash('sha1').update(string1).digest('hex');
  
  // 最後把完整的配置返給前端
  ctx.body = {
    appid: conf.appid,
    timestamp: timestamp,
    noncestr: noncestr,
    signature: signature,
  }
});

第三步:前端注入配置信息,調用微信api實現功能

這裏就是前端比較好理解的不分了

{
	mehtods: {
    async getJsConfig() {
      let res = await axios.get('/getJsConfig', {
        params: {
          url: location.href
        }
      })

      console.log(res)
      res.data.jsApiList = ['chooseImage'];
      wx.config(res.data);
      wx.ready(function() {
				// 在這裏調用api
      })

    },
	}
}

好了,以上就是微信校驗的全部內容了。如有疑問,歡迎與作者聯繫

一組庫

最後再安利一組微信開發的庫,co-wechat、co-wechat-api、co-wechat-oauth。這三個庫就是三兄弟,能解決公衆號開發不同階段的問題。比如我們可能經常要手動拼接微信提供的接口和我們的參數,並且手動發送請求,重複這麼弄還是挺費神的。有了這三兄弟,請求地址和發送請求一次性給你搞定,甚至連token存取到數據庫的操作也幫你做了。

1、co-wechat 主要用於微信消息接口

微信消息接口通過co-wechat實現

const wechat = require("co-wechat");

router.all(
  "/wechat",
  wechat(conf).middleware(async message => {
    console.log("wechat", message);
    return "hello world!" + message.Content;
  })
);

第一部分講到的消息接口通過co-wechat實現就這麼簡單

2、co-wechat-api 主要用於服務端接口

示例代碼:

const wechatApi = require("co-wechat-api");
const { ServerToken } = require("./mongoose");
const conf = require("./conf.js");

const api = new wechatApi(
  conf.appid,
  conf.appsecret,
  // 從數據庫取token的方法
  async () => await ServerToken.findOne(),
  // 從數據庫存token的方法
  async token => await ServerToken.updateOne({}, token, { upsert: true })
);

router.get("/getFollowers", async ctx => {
  console.log(ctx.url);
  // 使用的時候直接調用方法就可以了
  let res = await api.getFollowers();

  res = await api.batchGetUsers(res.data.openid, "zh_CN");
  console.log("followers: ", res);
  ctx.body = res;
});

3、co-wechat-oauth 主要用於網頁端授權登錄相關

示例代碼:

const oauth = require("co-wechat-oauth");
const client = new oauth(
  conf.appid,
  conf.appsecret,
  // 從數據庫取token的方法
  async openid => {
    return await ClientToken.getToken(openid);
  },
  // 從數據庫存token的方法
  async (openid, token) => {
    return await ClientToken.setToken(openid, token);
  }
);

router.get("/wxAuthorize", async ctx => {
  const state = ctx.query.id;
  const scope = "snsapi_userinfo";
  console.log("href: ", ctx.href);

  const path = new URL(ctx.href);
  const redirectUrl = `${path.protocol}//${path.hostname}/wxCallback`;

  const url = client.getAuthorizeURL(redirectUrl, state, scope);
  ctx.redirect(url);
});

router.get("/wxCallback", async ctx => {
  const code = ctx.query.code;
  console.log("wxCallback...");

  const res = await client.getAccessToken(code);

  console.log("token_res: ", res);

  ctx.redirect("/?openid=" + res.data.openid);
});

router.get("/getUser", async ctx => {
  const openid = ctx.query.openid;
  console.log("openid: " + openid);
  const res = await client.getUser(openid, "zh_CN");

  ctx.body = res;
});

好了,以上就是本次分享的全部內容,非常感謝您能認真閱讀本文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章