云開發(fā) 安全規(guī)則

2021-09-18 16:47 更新

安全規(guī)則是一個可以靈活地自定義數(shù)據(jù)庫云存儲讀寫權(quán)限的權(quán)限控制方式,通過配置安全規(guī)則,開發(fā)者可以在小程序端、網(wǎng)頁端精細(xì)化的控制云存儲和集合中所有記錄的增、刪、改、查權(quán)限,自動拒絕不符合安全規(guī)則的前端數(shù)據(jù)庫與云存儲請求,保障數(shù)據(jù)和文件安全。

一、{openid} 變量

在前面我們建議使用安全規(guī)則取代簡易版的權(quán)限設(shè)置,當(dāng)使用安全規(guī)則之后,這里有一個重要的核心就是 {openid} 變量 ,無論在前端(小程序端、web端)查詢時,它都是必不可少的(也就是說云函數(shù),云開發(fā)控制臺不受安全規(guī)則控制)。

1、查詢寫入都需明確指定 openid

{openid} 變量在小程序端使用時無需先通過云函數(shù)獲取用戶的 openid,直接使用'{openid}'即可,而我們在查詢時都需要顯式傳入openid。之前我們使用簡易權(quán)限配置時不需要這么做,這是因為查詢時會默認(rèn)給查詢條件加上一條 _openid 必須等于用戶 openid,但是使用安全規(guī)則之后,就沒有這個默認(rèn)的查詢條件了。

比如我們在查詢collection時,都需要在where里面添加如下如下的條件,{openid}變量就會附帶當(dāng)前用戶的openid。

db.collection('china').where({
  _openid: '{openid}', //安全規(guī)則里有auth.openid時都需要添加
})

更新、刪除等數(shù)據(jù)庫的寫入請求也都需要明確在where里添加這樣的一個條件(使用安全規(guī)則后,在小程序端也可以進行批量更新和刪除)。

db.collection('goods').where({
  _openid: '{openid}',
  category: 'mobile'
}).update({ //批量更新
  data:{
    price: _.inc(1)
  }
})

開啟安全規(guī)則之后,都需要在where查詢條件里指定_openid: '{openid}',這是因為大多數(shù)安全規(guī)則里都有auth.openid,也就是對用戶的身份有要求,where查詢條件為安全規(guī)則的子集,所以都需要添加。當(dāng)然你也可以根據(jù)你的情況,安全規(guī)則不要求用戶的身份,也就可以不傳入_openid: '{openid}'了。

2、doc 操作需轉(zhuǎn)為 where 操作

由于我們在進行執(zhí)行doc操作db.collection('china').doc(id)時,沒法傳入openid的這個條件,那應(yīng)該怎么控制權(quán)限呢?這時候,我們可以把doc操作都轉(zhuǎn)化為where操作就可以了,在where查詢里指定 _id 的值,這樣就只會查詢到一條記錄了:

db.collection('china').where({
  _id: 'tcb20200501',  //條件里面加_id
  _openid: '{openid}', //安全規(guī)則里有auth.openid時都需要添加
})

至于其他的doc操作,都需要轉(zhuǎn)化為基于collection的where操作,也就是說以后不再使用doc操作db.collection('china').doc(id)了。其中doc.update、doc.get和doc.remove可以用基于collection的update、get、remove取代,doc.set可以被更新指令_.set取代。當(dāng)然安全規(guī)則只適用于前端(小程序端或Web端),后端不受安全規(guī)則的權(quán)限限制。

3、嵌套數(shù)組對象里的openid

在使用簡易權(quán)限配置時,用戶在小程序端往數(shù)據(jù)庫里寫入數(shù)據(jù)時,都會給記錄doc里添加一個_openid的字段來記錄用戶的openid,使用安全規(guī)則之后同樣也是如此。在創(chuàng)建記錄時,可以把{openid}變量賦值給非_openid的字段或者寫入到嵌套數(shù)組里,后臺寫入記錄時發(fā)現(xiàn)該字符串時會自動替換為小程序用戶的 openid:

db.collection('posts').add({
  data:{
    books:[{
      title:"云開發(fā)快速入門",
      author:'{openid}'
    },{
      title:"數(shù)據(jù)庫入門與實戰(zhàn)",
      author:'{openid}'
    }]
  }
})

以往要進行openid的寫入操作時需要先通過云函數(shù)返回用戶openid,使用安全規(guī)則之后,直接使用{openid}變量即可,不過該方法僅支持add添加一條記錄時,不支持update的方式。

二、安全規(guī)則的寫法

使用安全規(guī)則之后,我們可以在控制臺(開發(fā)者工具和網(wǎng)頁)對每個集合以及云存儲的文件夾分別配置安全規(guī)則,也就是自定義權(quán)限,配置的格式是json,仍然嚴(yán)格遵循json配置文件的寫法(比如數(shù)組最后一項不能有逗號,,配置文件里不能有注釋等)。

1、粒度更細(xì)的增刪改查

我們先來看簡易權(quán)限配置所有用戶可讀,僅創(chuàng)建者可寫僅創(chuàng)建者可讀寫、所有用戶可讀所有用戶不可讀寫所對應(yīng)的安全規(guī)則的寫法,這個json配置文件的key表示操作類型,value是一個表達(dá)式,也是一個條件,解析為true時表示相應(yīng)的操作符合安全規(guī)則。

// 所有人可讀,僅創(chuàng)建者可讀寫
{
  "read": true,
  "write": "doc._openid == auth.openid"
}


//僅創(chuàng)建者可讀寫
{
  "read": "doc._openid == auth.openid",
  "write": "doc._openid == auth.openid"
}


//所有人可讀
{
  "read": true,
  "write": false
}


//所有用戶不可讀寫
{
  "read": false,
  "write": false
}

簡易的權(quán)限配置只有讀read與寫write,而使用安全規(guī)則之后,支持權(quán)限操作有除了讀與寫外,還將寫權(quán)限細(xì)分為create新建、update更新、delete刪除,也就是既可以只使用寫,也可以細(xì)分為增、刪、改,比如下面的案例為 所有人可讀,創(chuàng)建者可寫可更新,但是不能刪除

  "read": true,
  "create":"auth.openid == doc._openid",
  "update":"auth.openid == doc._openid",
  "delete":false 

操作類型無外乎增刪改查,不過安全規(guī)則的value是條件表達(dá)式,寫法很多,讓安全規(guī)則也就更加靈活。值得一提的是,如果我們不給read或者write賦值,它們的默認(rèn)值為false。

2、所有用戶可讀可寫的應(yīng)用

安全規(guī)則還可以配置所有人可讀可寫的類型,也就是如下的寫法,讓所有登錄用戶(用戶登錄了之后才有openid,即openid不為空)可以對數(shù)據(jù)可讀可寫。

{
  "read": "auth.openid != null", 
  "write": "auth.openid != null"
}

在小程序端,我們可以把數(shù)據(jù)庫集合的安全規(guī)則操作read和write都寫為true(這是所有人可讀可寫,而這里強調(diào)的是所有用戶),因為只要用戶使用開啟了云開發(fā)的小程序,就會免鑒權(quán)登錄有了openid,但是上面安全規(guī)則的寫法則通用于云存儲、網(wǎng)頁端的安全規(guī)則。

集合里的數(shù)據(jù)讓所有用戶可讀可寫在很多方面都有應(yīng)用,尤其是我們希望有其他用戶可以對嵌套數(shù)組和嵌套對象里的字段進行更新時。比如集合posts存儲的是所有資訊文章,而我們會把文章的評論嵌套在集合里。

{
  _id:"tcb20200503112",
  _openid:"用戶A", //用戶A也是作者,他發(fā)表的文章
  title:"云開發(fā)安全規(guī)則的使用經(jīng)驗總結(jié)",
  stars:223,
  comments:[{
    _openid:"用戶B", 
    comment:"好文章,作者有心了",
  }]
}

當(dāng)用戶A發(fā)表文章時,也就會創(chuàng)建這條記錄,如果用戶B希望可以評論(往數(shù)組comments里更新數(shù)據(jù))、點贊文章(使用inc原子更新更新stars的值),就需要對該記錄可讀可寫(至少是可以更新)。這在簡易權(quán)限配置是無法做到的(只能使用云函數(shù)來操作),有了安全規(guī)則之后,一條記錄就可以有被多個人同時維護的權(quán)限,而這樣的場景在云開發(fā)這種文檔型數(shù)據(jù)庫里比較常見(因為涉及到嵌套數(shù)組嵌套對象)。

安全規(guī)則與查詢where里的條件是相互配合的,但是兩者之間又有一定的區(qū)別。所有安全規(guī)則的語句指向的都是符合條件的文檔記錄,而不是集合。使用了安全規(guī)則的where查詢會先對文檔進行安全規(guī)則的匹配,比如小程序端使用where查詢不到記錄,就會報錯errCode: -502003 database permission denied | errMsg: Permission denied,然后再進行條件匹配,比如安全規(guī)則設(shè)置為所有人可讀時,當(dāng)沒有符合條件的結(jié)果時,會顯示查詢的結(jié)果為0。我們要注意無權(quán)查詢和查詢結(jié)果為0的區(qū)別。

3、全局變量

要搞清楚安全規(guī)則寫法的意思,我們還需要了解一些全局變量,比如前面提及的auth.openid表示的是登錄用戶的openid,而doc._openid表示的是當(dāng)前記錄_openid這個字段的值,當(dāng)用戶的openid與當(dāng)前記錄的_openid值相同時,就對該記錄有權(quán)限。全局變量還有now(當(dāng)前時間戳)和resource(云存儲相關(guān))。

變量 類型 說明
auth object 用戶登錄信息,auth.openid 也就是用戶的openid,如果是在web端它還有l(wèi)oginType登錄方式、uid等值
doc object 表示當(dāng)前記錄的內(nèi)容,用于匹配記錄內(nèi)容/查詢條件
now number 當(dāng)前時間的時間戳,也就是以從計時原點開始計算的毫秒
resource object resource.openid為云存儲文件私有歸屬標(biāo)識,標(biāo)記所有者的openid

4、運算符

安全規(guī)則的表達(dá)式還支持運算符,比如等于==,不等于!=,大于>,大于等于>=,小于<,小于等于<=,與&&,或||等等,后面會有具體的介紹。

運算符 說明 示例
== 等于 auth.openid == 'zzz' 用戶的 openid 為 zzz
!= 不等于 auth.openid != 'zzz' 用戶的 openid 不為 zzz
> 大于 doc.age>10 查詢條件的 age 屬性大于 10
>= 大于等于 doc.age>=10 查詢條件的 age 屬性大于等于 10
< 小于 doc.age<10 查詢條件的 age 屬性小于 10
<= 小于等于 doc.age<=10 查詢條件的 age 屬性小于等于 10
in 存在在集合中 auth.openid in ['zzz','aaa'] 用戶的 openid 是['zzz','aaa']中的一個
!(xx in []) 不存在在集合中,使用 in 的方式描述 !(a in [1,2,3]) !(auth.openid in ['zzz','aaa']) 用戶的 openid 不是['zzz','aaa']中的任何一個
&& auth.openid == 'zzz' && doc.age>10 用戶的 openid 為 zzz 并且查詢條件的 age 屬性大于 10
|| auth.openid == 'zzz'|| doc.age>10 用戶的 openid 為 zzz 或者查詢條件的 age 屬性大于 10
. 對象元素訪問符 auth.openid 用戶的 openid
[] 數(shù)組訪問符屬性 doc.favorites[0] == 'zzz' 查詢條件的 favorites 數(shù)組字段的第一項的值等于 zzz

四、身份驗證

全局變量auth與doc的組合使用可以讓登錄用戶的權(quán)限依賴于記錄的某個字段,auth表示的是登錄用戶,而doc、resource則是云開發(fā)環(huán)境的資源相關(guān),使用安全規(guī)則之后用戶與數(shù)據(jù)庫、云存儲之間就有了聯(lián)系。resource只有resource.openid,而doc不只有_openid,還可以有很多個字段,也就讓數(shù)據(jù)庫的權(quán)限有了很大的靈活性,后面我們更多的是以doc全局變量為例。

1、記錄的創(chuàng)建者

auth.openid是當(dāng)前的登錄用戶,而記錄doc里的openid則可以讓該記錄與登錄用戶之間有緊密的聯(lián)系,或者可以說讓該記錄有了一個身份的驗證。一般來說doc._openid所表示的是該記錄的創(chuàng)建者的openid,簡易權(quán)限控制比較的也是當(dāng)前登錄用戶是否是該記錄的創(chuàng)建者(或者為更加開放且粗放的權(quán)限)。

//登錄用戶為記錄的創(chuàng)建者時,才有權(quán)限讀
"read": "auth.openid == doc._openid", 


//不允許記錄的創(chuàng)建者刪除記錄(只允許其他人刪除)
"delete": "auth.openid != doc._openid", 

安全規(guī)則和where查詢是配套使用的,如果你指定記錄的權(quán)限與創(chuàng)建者的openid有關(guān),你在前端的查詢條件的范圍就不能比安全規(guī)則的大(如果查詢條件的范圍比安全規(guī)則的范圍大就會出現(xiàn)database permission denied:

db.collection('集合id').where({
  _openid:'{openid}'  //有doc._openid,因此查詢條件里就需要有_openid這個條件,
  key:"value"
})
.get().then(res=>{
  console.log(res)
})

2、指定記錄的角色

1、把權(quán)限指定給某個人

安全規(guī)則的身份驗證則不會局限于記錄的創(chuàng)建者,登錄用戶的權(quán)限還可以依賴記錄的其他字段,我們還可以給記錄的權(quán)限指定為某一個人(非記錄的創(chuàng)建者),比如很多個學(xué)生提交了作業(yè)之后,會交給某一個老師審閱批改,老師需要對該記錄有讀寫的權(quán)限,在處理時,可以在學(xué)生提交作業(yè)(創(chuàng)建記錄doc)時時可以指定teacher的openid,只讓這個老師可以批閱,下面是文檔的結(jié)構(gòu)和安全規(guī)則示例:

//文檔的結(jié)構(gòu)
{
  _id:"handwork20201020",
  _openid:"學(xué)生的openid", //學(xué)生為記錄的創(chuàng)建者,
  teacher:"老師的openid" //該學(xué)生被指定的老師的openid
}


//安全規(guī)則
{
  "read": "doc.teacher == auth.openid || doc._openid == auth.openid", 
  "write": "doc.teacher == auth.openid || doc._openid == auth.openid", 
}

讓登錄用戶auth.openid依賴記錄的其他字段,在功能表現(xiàn)上相當(dāng)于給該記錄指定了一個角色,如直屬老師、批閱者、直接上級、閨蜜、夫妻、任務(wù)的直接指派等角色。

對于查詢或更新操作,輸入的where查詢條件必須是安全規(guī)則的子集,比如你的安全規(guī)則如果是doc.teacher == auth.openid,而你在where里沒有teacher:'{openid}'這樣的條件,就會出現(xiàn)權(quán)限報錯。

由于安全規(guī)則和where查詢需要配套使用,安全規(guī)則里有doc.teacherdoc._openid,在where里也就需要寫安全規(guī)則的子集條件,比如_openid:'{openid}'teacher:'{openid}',由于這里老師也是用戶,我們可以傳入如下條件讓學(xué)生和老師共用一個數(shù)據(jù)庫請求:

const db = wx.cloud.database()
const _ = db.command


//一條記錄可以同時被創(chuàng)建者(學(xué)生)和被指定的角色(老師)讀取
db.collection('集合id').where(_.or([
  {_openid:'{openid}' }, //與安全規(guī)則doc._openid == auth.openid對應(yīng)
  {teacher:'{openid}' } //與安全規(guī)則doc.teacher == auth.openid對應(yīng)
]))
.get().then(res=>{
  console.log(res)
})

2、把權(quán)限指定給某些人

上面的這個角色指定是一對一、或多對一的指定,也可以是一對多的指定,可以使用in!(xx in [])運算符。比如下面是可以給一個記錄指定多個角色(學(xué)生創(chuàng)建的記錄,多個老師有權(quán)讀寫):

//文檔的結(jié)構(gòu)
{
  _id:"handwork20201020",
  _openid:"學(xué)生的openid", //學(xué)生為記錄的創(chuàng)建者,
  teacher:["老師1的openid","老師2的openid","老師3的openid"] 
}


//安全規(guī)則
{
  "read": "auth.openid in doc.teacher || doc._openid == auth.openid", 
  "write": "auth.openid in doc.teacher || doc._openid == auth.openid", 
}

這里要再強調(diào)的是前端(小程序端)的where條件必須是安全規(guī)則權(quán)限的子集,比如我們在小程序端針對老師進行如下查詢('{openid}'不支持查詢指令,需要后端獲取)

db.collection('集合id').where({
  _openid:'{openid}',
  teacher:_.elemMatch(_.eq('老師的openid'))
}).get()
.then(res=>{
  console.log(res)
})

前面我們實現(xiàn)了將記錄的權(quán)限指定給某個人或某幾個人,那如何將記錄的權(quán)限指定給某類人呢?比如打車軟件為了數(shù)據(jù)的安全性會有司機、乘客、管理員、開發(fā)人員、運維人員、市場人員等,這都需要我們在數(shù)據(jù)庫里新建一個字段來存儲用戶的類型,比如{role:3},用1、2、3、4等數(shù)字來標(biāo)明,或者用{isManager:true}boolean類型來標(biāo)明,這個新增的字段可以就在查詢的集合文檔里doc.role,或者是一個單獨的集合(也就是存儲權(quán)限的集合和要查詢的集合是分離的,這需要使用get函數(shù)跨集合查詢),后面會有具體介紹。

3、doc.auth與文檔的創(chuàng)建者

下面有一個例子可以加深我們對安全規(guī)則的理解,比如我們在記錄里指定文檔的auth為其他人的openid,并配上與之相應(yīng)的安全規(guī)則,即使當(dāng)前用戶實際上就是這個記錄的創(chuàng)建者,這個記錄有該創(chuàng)建者的_openid,他也沒有操作的權(quán)限。安全規(guī)則會對查詢條件進行評估,只要符合安全規(guī)則,查詢才會成功,違反安全規(guī)則,查詢就會失敗。

//文檔的結(jié)構(gòu),比如以下為一條記錄
{
  _id:"handwork20201020",
  _openid:"創(chuàng)建者的openid", 
  auth:"指定的auth的openid" 
}


//安全規(guī)則
{
  "權(quán)限操作": "auth.openid == doc.auth" //權(quán)限操作為read、write、update等
}


//前端查詢,不符合安全規(guī)則,即使是記錄的創(chuàng)建者也沒有權(quán)限
db.collection('集合id').where({
  auth:'{openid}'
})

四、安全規(guī)則常用場景

簡易版權(quán)限設(shè)置沒法在前端實現(xiàn)記錄跨用戶的寫權(quán)限(含update、create、delete),也就是說記錄只有創(chuàng)建者可寫。而文檔型數(shù)據(jù)庫一個記錄因為反范式化嵌套的原因可以承載的信息非常多,B用戶操作A用戶創(chuàng)建的記錄,尤其是使用更新指令update字段以及內(nèi)嵌字段的值這樣的場景是非常常見的。除此之外,僅安全規(guī)則可以實現(xiàn)前端對記錄的批量更新和刪除。

比如我們可以把評論、收藏、點贊、轉(zhuǎn)發(fā)、閱讀量等信息內(nèi)嵌到文章的集合里,以往我們在小程序端(只能通過云函數(shù))是沒法讓B用戶對A用戶創(chuàng)建的記錄進行操作,比如點贊、收藏、轉(zhuǎn)發(fā)時用更新指令inc更新次數(shù),比如沒法直接用更新指令將評論push寫入到記錄里:

{
  _id:"post20200515001",
  title:"云開發(fā)安全規(guī)則實戰(zhàn)",
  star:221, //點贊數(shù)
  comments:[{    //評論和子評論
    content:"安全規(guī)則確實是非常好用",
    nickName:"小明",
    subcomment:[{
      content:"我也這么覺得",
      nickName:"小軍",
    }]
  }],
  share:12, //轉(zhuǎn)發(fā)數(shù)
  collect:15 //收藏數(shù)
  readNum:2335 //閱讀量
}

在開啟安全規(guī)則,我們就可以直接在前端讓B用戶修改A用戶創(chuàng)建的記錄,這樣用戶閱讀、點贊、評論、轉(zhuǎn)發(fā)、收藏文章等時,就可以直接使用更新指令對文章進行字段級別的更新。

"read":"auth.openid != null",
"update":"auth.openid != null"

這個安全規(guī)則相比于所有人可讀,僅創(chuàng)建者可讀寫,開放了update的權(quán)限,小程序端也有l(wèi)imit 20的限制。而如果不使用安全規(guī)則,把這些放在云函數(shù)里進行處理不僅處理速度更慢,而且非常消耗云函數(shù)的資源。

db.collection('post').where({
  _id:"post20200515001",
  openid:'{openid}'
}).update({
  data:{
    //更新指令的應(yīng)用
  }
})

五、數(shù)據(jù)驗證doc的規(guī)則匹配

我們還可以把訪問權(quán)限的控制信息以字段的形式存儲在數(shù)據(jù)庫的集合文檔里,而安全規(guī)則可以根據(jù)文檔數(shù)據(jù)動態(tài)地允許或拒絕訪問,也就是說doc的規(guī)則匹配可以讓記錄的權(quán)限動態(tài)依賴于記錄的某一個字段的值。

doc規(guī)則匹配的安全規(guī)則針對的是整個集合,而且要求集合里的所有記錄都有相應(yīng)的權(quán)限字段,而只有在權(quán)限字段滿足一定條件時,記錄才有權(quán)限被增刪改查,是一個將集合的權(quán)限范圍按照條件要求收窄的過程,where查詢時的條件不能比安全規(guī)則規(guī)定的范圍大(查詢條件為安全規(guī)則子集);配置了安全規(guī)則的集合里的記錄只有兩種狀態(tài),有權(quán)限和沒有權(quán)限。

這里仍然再強調(diào)的是使用where查詢時要求查詢條件是安全規(guī)則的子集,在進行where查詢前會先解析規(guī)則與查詢條件進行校驗,如果where條件不是安全規(guī)則的子集就會出現(xiàn)權(quán)限報錯,不能把安全規(guī)則看成是一個篩選條件,而是一個保護記錄數(shù)據(jù)安全的不可逾越的規(guī)則。

1、記錄的狀態(tài)權(quán)限

doc的規(guī)則匹配,特別適合每個記錄存在多個狀態(tài)或每個記錄都有一致的權(quán)限條件(要么全部是,要么全部否),而只有一個狀態(tài)或滿足條件才有權(quán)限被用戶增刪改查時的情形,比如文件審批生效(之前存在審批沒有生效的多個狀態(tài)),文章的發(fā)布狀態(tài)為pubic(之前為private或其他狀態(tài)),商品的上架(在上架前有多個狀態(tài)),文字圖片內(nèi)容的安全檢測不違規(guī)(之前在進行后置校驗),消息是否撤回,文件是否刪除,由于每個記錄我們都需要標(biāo)記權(quán)限,而只有符合條件的記錄才有被增刪改查的機會。

比如資訊文章的字段如下,每個記錄對應(yīng)著一篇文章,而status則存儲著文章的多個狀態(tài),只有public時,文章才能被用戶查閱到,我們可以使用安全規(guī)則"read": "doc.status=='public'"。而對于軟刪除(文章假刪除),被刪除可以作為一個狀態(tài),但是文章還是在數(shù)據(jù)庫里。

{
  _id:"post2020051314",
  title:"云開發(fā)發(fā)布新能力,支持微信支付云調(diào)用",
  status:"public"
},
{
  _id:"post2020051312",
  title:"云函數(shù)灰度能力上線",
  status:"edit"
},
{
  _id:"post2020051311",
  title:"云開發(fā)安全規(guī)則深度研究",
  status:"delete"
}

而在前端(小程序端)與之對應(yīng)的數(shù)據(jù)庫查詢條件則必須為安全規(guī)則的子集,也就是說安全規(guī)則不能作為你查詢的過濾條件,安全規(guī)則會對查詢進行評估,如果查詢不符合安全規(guī)則設(shè)置的約束(非子集),那么前端的查詢請求沒有權(quán)限讀取文檔,而不是過濾文檔:

db.collection('集合id').where({
  status:"public"  //你不能不寫這個條件,而指望安全規(guī)則給你過濾
}).get()
.then(res=>{
  console.log(res)
})

2、記錄禁止為空

有時候我們需要對某些記錄有著非常嚴(yán)格的要求,禁止為空,如何為空一律不予被前端增刪改查,比如已經(jīng)上架的shop集合里的商品列表,有些核心數(shù)據(jù)如價格、利潤、庫存等就不能為空,給企業(yè)造成損失,相應(yīng)的安全規(guī)則和查詢?nèi)缦拢?/p>

//安全規(guī)則
{
  "權(quán)限操作": "doc.profit != null",
}


//權(quán)限操作,profit = 0.65就是安全規(guī)則的子集
db.collection('shop').where({
  profit:_.eq(0.65)
})

3、記錄的子集權(quán)限

安全規(guī)則記錄的字段值不僅限于一個狀態(tài)(字符串類型),還可以是可以運算的范圍值,如大于>,小于<in等,比如商品的客單價都是100以上,管理員在后端(控制臺,云函數(shù)等)把原本190元的價格寫成了19,或者失誤把價格寫成了負(fù)數(shù),這種情況下我們對商品集合使用安全規(guī)則doc.price > 100,前端將失去所有價格低于100的商品的操作權(quán)限,包括查詢。

//安全規(guī)則
"操作權(quán)限":"doc.price > 100"


//相應(yīng)的查詢
db.collection('shop').where({
  price:_eq(125)
})

安全規(guī)則的全局變量now表示的是當(dāng)前時間的時間戳,這讓安全規(guī)則可以給權(quán)限的時間節(jié)點和權(quán)限的時效性設(shè)置一些規(guī)則,這里就不具體講述了。

五、全局函數(shù)get構(gòu)建權(quán)限體系

全局函數(shù)get可以實現(xiàn)跨集合來限制權(quán)限。doc的權(quán)限匹配更多的是基于文檔性質(zhì)的權(quán)限,也就是集合內(nèi)所有文檔都有相同的字段,根據(jù)這個字段的值的不同來劃分權(quán)限。但是有時候我們希望實現(xiàn)多個用戶和多個用戶角色來管理集合的文檔,擁有不同的權(quán)限,如果把用戶和角色都寫進文檔的每個記錄里,就會非常難以管理。也就是說doc的權(quán)限匹配并不適合復(fù)雜的用戶管理文檔的權(quán)限體系。

我們可以把單個復(fù)雜的集合文檔(反范式化的設(shè)計)拆分成多個集合文檔(范式化設(shè)計),將用戶和角色從文檔里分離出來。比如博客有文章post集合,而user集合除了可以把用戶劃分為作者、編輯、投稿者這樣的用戶身份,還可以是管理員組,編輯組等。如果我們把記錄的權(quán)限賦予給的人員比較多或群組比較復(fù)雜,則需要把角色存儲在其獨立的集合中,而不是作為目標(biāo)文檔中的一個字段,用全局函數(shù)get來實現(xiàn)跨集合的權(quán)限限制。

get 函數(shù)是全局函數(shù),可以跨集合來獲取指定的記錄,用于在安全規(guī)則中獲取跨集合的記錄來參與到安全規(guī)則的匹配中,get函數(shù)的參數(shù)格式是 database.集合名.記錄id

比如我們可以給文章post集合設(shè)置如下安全規(guī)則,只有管理員才可以刪除記錄,而判斷用戶是否為管理員則需要跨集合用user集合里的字段值來判斷:

//user集合的結(jié)構(gòu)
{
  _id:"oUL-m5FuRmuVmxvbYOGuXbuEDsn8", //用戶的openid
  isManager:true
}


//post集合的權(quán)限
{
  "read": "true",
  "delete": "get(`database.user.${auth.openid}`).isManager== true"
}
db.collection('post').where({
  //相應(yīng)的條件,并不受子集的限制
})

get函數(shù)還可以接收變量,值可以通過多種計算方式得到,例如使用字符串模版進行拼接,這是一個查詢的過程,如果相應(yīng)的文檔里有記錄,則函數(shù)返回記錄的內(nèi)容,否則返回空(注意反引號的寫法):

`(database.${doc.collction}.${doc._id})`

get函數(shù)的限制條件

  • 安全規(guī)則里的get函數(shù) 參數(shù)中存在的變量 doc 需要在 query 條件中以 == 或 in 方式出現(xiàn),若以 in 方式出現(xiàn),只允許 in 唯一值, 即 doc.shopId in array, array.length == 1

  • 一個表達(dá)式最多可以有 3 個 get 函數(shù),最多可以訪問 3 個不同的文檔。

  • get 函數(shù)的嵌套深度最多為 2, 即 get(get(path))。

讀操作觸發(fā)與配額消耗說明

get 函數(shù)的執(zhí)行會計入數(shù)據(jù)庫請求數(shù),同樣受數(shù)據(jù)庫配額限制。在未使用變量的情況下,每個 get 會產(chǎn)生一次讀操作,在使用變量時,對每個變量值會產(chǎn)生一次 get 讀操作。例如:

假設(shè)某集合 shop 上有如下規(guī)則:

{
  "read": "auth.openid == get(`database.shop.${doc._id}`).owner",
  "write": false
}

在執(zhí)行如下查詢語句時會產(chǎn)生 5 次讀取。

db.collection('shop').where(_.or([{_id:1},{_id:2},{_id:3},{_id:4},{_id:5}])).get()
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號