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

2020-07-22 15:33 更新

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

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

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

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

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

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

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

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

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

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

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

然后使用VS Code新建一個(gè)云函數(shù)比如weblogin云函數(shù)專門用來處理網(wǎng)頁的登錄,將私鑰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ù),代碼的邏輯如下:

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

  • 如果userId存在,說明用戶填寫的userId是對的;

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

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

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

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ù)庫查詢是否存在該userId
      const users = (await db.collection('users').where({
        userId:userId
      }).get()).data


      if(users.length != 0){  //當(dāng)數(shù)據(jù)庫存在該userId時(shí),users為一個(gè)數(shù)組,數(shù)組長度不為0
      //使用用戶的openid為customUserId來生成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ù)部署上傳之后,然后開啟云接入(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來登錄呢?我們還是使用axios進(jìn)行HTTP請求,所以在我們的前端頁面,比如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里的頁面生命周期函數(shù)window.onload= function(){//生命周期函數(shù)}里輸入以下代碼,首先返回用戶的登錄態(tài)LoginState來判斷用戶是否已經(jīng)登錄,如果用戶沒有登錄,則發(fā)起HTTP請求,獲取云接入返回的ticket,然后使用auth.customAuthProvider().signIn(ticket)用自定義登錄憑證ticket來登錄云開發(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("登錄成功")
          //登錄成功后,就可以操作云開發(fā)環(huán)境里的各種資源啦
        })
        .catch(err => {
          console.log("登錄失敗",err)
        });
    }).catch(err => {
        console.log(err)
    })
  }else{
    console.log("您已經(jīng)登錄啦")
  }
}
login()

二、web端賬號與賬號的打通

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

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

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

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

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

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

3、小程序端和Web端賬號打通

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

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

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

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

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

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

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

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

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

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

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

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

所以,我們可以將安全規(guī)則設(shè)置為如下,無論記錄是在小程序端創(chuàng)建還是web端創(chuàng)建,用戶都擁有跨端的可讀寫權(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è)字段來做過渡,我們也可以只用一個(gè)字段,比如只用一個(gè)uid的字段,當(dāng)記錄_openid是小程序的_openid時(shí),uid就記錄web端用戶的uid;當(dāng)記錄_openid是web端用戶的uid時(shí),uid就記錄該用戶在小程序的openid,安全規(guī)則就可以寫為:

{
  "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)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號