云開(kāi)發(fā) 自定義登錄

2020-07-22 15:33 更新

匿名登錄可以讓用戶在無(wú)需注冊(cè)登錄的情況下在短期內(nèi)使用數(shù)據(jù)庫(kù)、云存儲(chǔ)以及調(diào)用云函數(shù),但是大多數(shù)的應(yīng)用是需要獲取用戶的身份才能更長(zhǎng)期更安全將數(shù)據(jù)存儲(chǔ)在云端,并且可以獲取跨設(shè)備跨端的一致性的體驗(yàn)。在Web端我們可以使用自定義登錄與匿名登錄相結(jié)合;在微信小程序端,借助于云開(kāi)發(fā),無(wú)需額外操作便可免鑒權(quán)登錄(實(shí)際上就是openId),要實(shí)現(xiàn)跨端一致,就需要考慮免鑒權(quán)登錄與自定義登錄相結(jié)合。

一、自定義登錄與云開(kāi)發(fā)

微信小程序云開(kāi)發(fā)有一套免鑒權(quán)的賬號(hào)體系openid,我們可以基于這套賬號(hào)體系結(jié)合自定義登錄實(shí)現(xiàn)Web端和小程序端的跨端登錄和權(quán)限控制。

1、創(chuàng)建一個(gè)用戶集合和記錄

為了學(xué)習(xí)的方便,我們先假定(或者模擬)用戶已經(jīng)使用過(guò)我們的小程序并留有userId和openid,打開(kāi)小程序云開(kāi)發(fā)數(shù)據(jù)庫(kù)在數(shù)據(jù)庫(kù)里新建一個(gè)集合,集合的名稱為users,在users里新建一個(gè)記錄,比如:

{
  _openid:"oUL-m5FuRmuVmxvbYOGnXbnEDsn8",
  userId:"lidongbbsky",
}

由于小程序使用的是云開(kāi)發(fā),用戶無(wú)需注冊(cè)登錄就可以調(diào)用云開(kāi)發(fā)環(huán)境里的資源,那當(dāng)這個(gè)用戶到Web網(wǎng)頁(yè)上時(shí),應(yīng)該怎么樣才能登錄以前的賬號(hào)呢?只需要在網(wǎng)頁(yè)上輸入userId即可登錄。

直接輸入上面這個(gè)userId不輸入密碼就可以登錄,這是一個(gè)不安全的做法,不過(guò)安全的做法也不一定需要密碼,我們可以使用云函數(shù)每隔十幾秒動(dòng)態(tài)刷新userId來(lái)取代用戶名+密碼這種傳統(tǒng)方式,比如userId為openid的后三位+只有10幾秒生命周期的動(dòng)態(tài)三位數(shù)(有點(diǎn)類似于短信的動(dòng)態(tài)驗(yàn)證碼),而用戶userId的獲取只能登錄到小程序來(lái)獲取,這樣用戶只需要輸入6位數(shù),既方便且安全。當(dāng)然你也可以用其他方式來(lái)生成userId。

通過(guò)數(shù)據(jù)庫(kù),我們把userId和小程序的唯一openid關(guān)聯(lián)到了一起,那在web網(wǎng)頁(yè)上又是怎樣實(shí)現(xiàn)userId的登錄呢?又是如何保證登錄的安全性的呢?

2、獲取私鑰并編寫(xiě) ticket 創(chuàng)建模塊

打開(kāi)騰訊云云開(kāi)發(fā)網(wǎng)頁(yè)控制臺(tái),在【環(huán)境】-【環(huán)境設(shè)置】-【登錄方式】,單擊私鑰下載,私鑰是一份 JSON 格式的數(shù)據(jù),里面包含private_key_idprivate_key。接下來(lái)我們會(huì)用云函數(shù)把openid生成唯一用戶ID(稱之為customUserId)結(jié)合這個(gè)私鑰文件計(jì)算出云開(kāi)發(fā)的自定義登錄憑證ticket,最后使用ticket登錄。

然后使用VS Code新建一個(gè)云函數(shù)比如weblogin云函數(shù)專門(mén)用來(lái)處理網(wǎng)頁(yè)的登錄,將私鑰json文件的名稱自定義一下,比如tcb_custom_login.json保存到與云函數(shù)的目錄里。

├── weblogin  //weblogin云函數(shù)目錄
│   └── index.js
│   └── config.json
│   └── package.json
│   └── tcb_custom_login.json //下載的私鑰json文件

然后再在index.js里輸入如下代碼,創(chuàng)建一個(gè)生成ticket的服務(wù),代碼的邏輯如下:

  • 首先會(huì)獲取用戶在web頁(yè)面填寫(xiě)的userId,如果這個(gè)userId非空,我們就去數(shù)據(jù)庫(kù)查詢這個(gè)userId是否存在;

  • 如果userId存在,說(shuō)明用戶填寫(xiě)的userId是對(duì)的;

  • 查詢這個(gè)用戶的openid,openid是用戶的唯一ID,但是customUserId里不能有特殊有特殊符號(hào),所以我們會(huì)把去掉openid的連接符作為customUserId;

  • 然后用createTicket讓customUserId再來(lái)結(jié)合密鑰生成ticket,而且這個(gè)ticket是每隔10分鐘會(huì)刷新;

  • 再把ticket以集成請(qǐng)求的方式發(fā)送給web端,這樣web端再來(lái)根據(jù)這個(gè)ticket來(lái)登錄

const tcb = require('@cloudbase/node-sdk')
const app = tcb.init({
  env: 'xly-xrlur',
  credentials: require('./tcb_CustomLoginKeys.json')
})
const db = tcb.database();


exports.main = async (event, context) => {
  const userId = event.queryStringParameters.userId //從web端傳入的userId
  try{
    if( userId != null){  //如果web端傳入的userId非空,就從數(shù)據(jù)庫(kù)查詢是否存在該userId
      const users = (await db.collection('users').where({
        userId:userId
      }).get()).data


      if(users.length != 0){  //當(dāng)數(shù)據(jù)庫(kù)存在該userId時(shí),users為一個(gè)數(shù)組,數(shù)組長(zhǎng)度不為0
      //使用用戶的openid為customUserId來(lái)生成ticket,因?yàn)閛penid有一個(gè)-連接符,把它給替換掉
        const customUserId = await (users[0]._openid).replace('-','') 
        const ticket = app.auth().createTicket(customUserId, {
          refresh: 10 * 60 * 1000 // 每十分鐘刷新一次登錄態(tài), 默認(rèn)為一小時(shí)
        });
        return {
          statusCode: 200,
          headers: {
            'content-type': 'application/json',
            'Access-Control-Allow-Origin':'*',
            'Access-Control-Allow-Methods':'*',/=
            'Access-Control-Allow-Headers':'Content-Type'
          },
          body: ticket
        }
      }
    }
  }catch(err){
    console.log(err)
  }
}

將weblogin云函數(shù)部署上傳之后,然后開(kāi)啟云接入(HTTP觸發(fā))并創(chuàng)建路由比如/weblogin,我們可以在瀏覽器里輸入以下地址(也就是在weblogin云接入里傳入?yún)?shù)userId的值為lidongbbsky)獲取到生成的ticket:

http://xly-xrlur.service.tcloudbase.com/weblogin?userId=lidongbbsky

3、web前端根據(jù)ticket登錄

我們已經(jīng)使用云函數(shù)生成了一個(gè)ticket,那前端又如何根據(jù)這個(gè)ticket來(lái)登錄呢?我們還是使用axios進(jìn)行HTTP請(qǐng)求,所以在我們的前端頁(yè)面,比如public文件夾下的index.html里先引入axios

<script src="https://imgcache.qq.com/qcloud/tcbjs/1.5.1/tcb.js" rel="external nofollow" ></script>
<script src="https://unpkg.com/axios/dist/axios.min.js" rel="external nofollow" ></script>
<script src="./js/main.js"></script>

然后再在main.js里的頁(yè)面生命周期函數(shù)window.onload= function(){//生命周期函數(shù)}里輸入以下代碼,首先返回用戶的登錄態(tài)LoginState來(lái)判斷用戶是否已經(jīng)登錄,如果用戶沒(méi)有登錄,則發(fā)起HTTP請(qǐng)求,獲取云接入返回的ticket,然后使用auth.customAuthProvider().signIn(ticket)用自定義登錄憑證ticket來(lái)登錄云開(kāi)發(fā):

const auth = app.auth({
    persistence: 'session' //在窗口關(guān)閉時(shí)清除身份驗(yàn)證狀態(tài)
})


async function login(){
  const loginState = app.auth().hasLoginState();
  if(!loginState){
    const url ="https://xly-xrlur.service.tcloudbase.com/weblogin"
    axios.get(url,{
      userId:"lidongbbsky"
    })
    .then(res => {
      auth.customAuthProvider()
        .signIn(res.data)
        .then(() => {
          console.log("登錄成功")
          //登錄成功后,就可以操作云開(kāi)發(fā)環(huán)境里的各種資源啦
        })
        .catch(err => {
          console.log("登錄失敗",err)
        });
    }).catch(err => {
        console.log(err)
    })
  }else{
    console.log("您已經(jīng)登錄啦")
  }
}
login()

二、web端賬號(hào)與賬號(hào)的打通

1、如何獲取web端openid(uid)

當(dāng)我們?cè)趙eb端登錄了之后,web端用戶也會(huì)一個(gè)類似于小程序的openid(但是不相同),那我們要如何獲取到這個(gè)openid呢?和小程序用戶一樣,當(dāng)我們往云存儲(chǔ)和數(shù)據(jù)庫(kù)里添加數(shù)據(jù)時(shí),就會(huì)自動(dòng)添加一個(gè)openid的字段,里面的值就是web端openid(uid)。

那除此之外,我們是否能夠像小程序云開(kāi)發(fā)一樣在云函數(shù)里獲取到web端用戶的openid呢?這個(gè)其實(shí)我們已經(jīng)在前面web端云開(kāi)發(fā)里的webtest云函數(shù)就已經(jīng)寫(xiě)了方法啦,這里再單獨(dú)拿出來(lái):

const tcb = require('@cloudbase/node-sdk')
const app = tcb.init({
  env: 'xly-xrlur'
})
const auth = app.auth()
exports.main = async (event, context) => {
  const {openId, uid, customUserId } = auth.getUserInfo()
  return {openId, uid, customUserId }
}

這里的uid就是web端用戶的openid,而openId則是微信用戶(小程序)的openid,customUserId就是前面我們用于生成ticket的customUserId。

2、web端和小程序openid的區(qū)別與聯(lián)系

當(dāng)用戶在web端使用customUserId自定義登錄之后也會(huì)有一個(gè)不同于小程序賬戶體系的openid,這個(gè)openid是用戶的uid,customUserId和uid是對(duì)應(yīng)的,只要customUserId不變,web端用戶的openid(uid)也不會(huì)變更。也就是說(shuō)由于我們的customUserId是根據(jù)小程序的openid生成的唯一且不隨設(shè)備不隨時(shí)間變更而變更的,那么web端的openid(uid)也不會(huì)因?yàn)樵O(shè)備和時(shí)間而變更。

盡管用戶在web端傳入的userId是可以動(dòng)態(tài)刷新的,但是在云函數(shù)里我們并沒(méi)有把這個(gè)可以動(dòng)態(tài)刷新的userId作為customUserId,所以不必?fù)?dān)心userId的不同,web端用戶在云開(kāi)發(fā)的openid會(huì)有所變化;ticket也是可以動(dòng)態(tài)刷新的,但是這只是加強(qiáng)賬號(hào)的安全性,并不會(huì)影響web端用戶的openid的唯一性。

web端用戶的openid(也就是uid)的唯一性,且不隨設(shè)備和時(shí)間的變更而變更的永久性是我們可以進(jìn)行跨設(shè)備操作的基礎(chǔ)。不過(guò)值得一提的是,即使是相同用戶web端的openid和小程序的openid雖然有關(guān)聯(lián),但是兩者之間是不同的賬號(hào)體系,如果我們要把小程序和web端的賬號(hào)打通則需要進(jìn)行一定的處理。

3、小程序端和Web端賬號(hào)打通

即使是相同的用戶,web端和小程序端的openid都是唯一且永久的,而且都還不同,那如果讓相同的用戶在Web端和小程序端有一致性的體驗(yàn)和相同的權(quán)限呢?我們知道云開(kāi)發(fā)的權(quán)限是非常依賴openid的,無(wú)論是數(shù)據(jù)庫(kù)的增刪改查,還是云存儲(chǔ)的增刪改查,都是根據(jù)openid來(lái)判斷用戶的權(quán)限的。賬號(hào)體系打通可能比較容易,但是權(quán)限又該如何控制呢?

比如用戶在小程序端創(chuàng)建了個(gè)人資料,發(fā)表了一篇文章,我們要打通賬號(hào),就要能讓該用戶在web端可以查看且能修改他的個(gè)人資料或文章數(shù)據(jù),比如下面是users集合里的一條記錄:

{
  _openid:"oUL-m5FuRmuVmxvbYOGnXbnEDsn8",
  userId:"lidongbbsky",
  userInfo:{
    name:"李東bbsky",
    title:"雜役"
  },
  posts:[{
    title:"為什么說(shuō)云開(kāi)發(fā)值得普及?",
    content:"<h3>學(xué)習(xí)門(mén)檻特別低</h3><p>可以說(shuō)云開(kāi)發(fā)是最容易上手且最容易出成果的編程方向了</p>"
  }]
}

當(dāng)我們把該集合設(shè)置為所有人可讀,僅創(chuàng)建者可讀寫(xiě)時(shí),用戶在小程序端對(duì)屬于自己的記錄可讀可寫(xiě),但是當(dāng)該用戶在web端時(shí),他只能讀不能寫(xiě),除非使用云函數(shù),先在數(shù)據(jù)庫(kù)里查詢到該用戶的openid(如果你把userId設(shè)計(jì)成動(dòng)態(tài)刷新的話),再進(jìn)行數(shù)據(jù)庫(kù)和存儲(chǔ)的增刪改查,也就是用戶對(duì)數(shù)據(jù)庫(kù)和云存儲(chǔ)的所有操作都需要經(jīng)過(guò)云函數(shù)都需要先查詢用戶在小程序端的openid,功能雖然可以實(shí)現(xiàn),但是對(duì)web端并不是很友好,一是多了一次查詢,二是不能在web端直接進(jìn)行寫(xiě)操作。

4、安全規(guī)則之openid與uid

如果想要不需要借助于云函數(shù)的情況下,讓web端的用戶能夠更加方便的和小程序端的用戶權(quán)限打通,則需要借助于安全規(guī)則,比如僅創(chuàng)建者可讀寫(xiě)的安全規(guī)則是:

{
  "read": "auth.openid == doc._openid",
  "write": "auth.openid == doc._openid"
}

這個(gè)安全規(guī)則讓小程序用戶的openid與記錄的_openid字段的值相同時(shí),就有了讀寫(xiě)權(quán)限。也就是auth.openid 是小程序用戶免登錄之后的openid。那如何讓web端用戶也有一樣的權(quán)限呢?我們可以給user的每一個(gè)記錄都新增一個(gè)webuid的字段,用來(lái)記錄web端用戶的openid(uid)以及一個(gè)wxuid的字段,用來(lái)記錄小程序端的openid。讓權(quán)限互通,這里會(huì)有四種情況:

  • 如果記錄A是用戶在小程序端創(chuàng)建的,那這條記錄自動(dòng)添加的_openid為小程序的openid,只要read、write的安全規(guī)則為auth.openid == doc._openid,那小程序用戶對(duì)這條記錄有讀寫(xiě)權(quán)限;

  • 如果該用戶想在web端對(duì)記錄A有讀寫(xiě)權(quán)限,那我們可以讓read、write的安全規(guī)則為auth.uid == doc.webuid,這樣webd端用戶就能對(duì)記錄有讀寫(xiě)權(quán)限;

  • 如果記錄B是用戶在web創(chuàng)建的,那這條記錄自動(dòng)添加的_openid為web端用戶的openid(uid),只要read、write的安全規(guī)則為auth.uid == doc._openid,那web端用戶對(duì)這條記錄有讀寫(xiě)權(quán)限;

  • 如果該用戶想在小程序端對(duì)記錄B有讀寫(xiě)權(quán)限,那我們可以讓read、write的安全規(guī)則為auth.openid == doc.wxuid,這樣webd端用戶就能對(duì)記錄有讀寫(xiě)權(quán)限;

所以,我們可以將安全規(guī)則設(shè)置為如下,無(wú)論記錄是在小程序端創(chuàng)建還是web端創(chuàng)建,用戶都擁有跨端的可讀寫(xiě)權(quán)限:

{
  "read": "auth.openid == doc._openid || auth.uid == doc.webuid || auth.uid == doc._openid || auth.openid == doc.wxuid",
  "write": "auth.openid == doc._openid || auth.uid == doc.webuid || auth.uid == doc._openid || auth.openid == doc.wxuid",
}

之所以這么復(fù)雜,是因?yàn)閣eb端創(chuàng)建記錄時(shí)的_openid是用戶的uid,小程序端創(chuàng)建記錄時(shí)的_openid是微信生態(tài)的_openid,而要做到兩套體系容易,則需要一個(gè)字段來(lái)做過(guò)渡,我們也可以只用一個(gè)字段,比如只用一個(gè)uid的字段,當(dāng)記錄_openid是小程序的_openid時(shí),uid就記錄web端用戶的uid;當(dāng)記錄_openid是web端用戶的uid時(shí),uid就記錄該用戶在小程序的openid,安全規(guī)則就可以寫(xiě)為:

{
  "read": "auth.openid == doc._openid || auth.uid == doc.uid || auth.uid == doc._openid || auth.openid == doc.uid",
  "write": "auth.openid == doc._openid || auth.uid == doc.uid || auth.uid == doc._openid || auth.openid == doc.uid"
}

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)