W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
數(shù)據(jù)庫的權(quán)限分為小程序端和管理端,管理端包括云函數(shù)端和控制臺(tái)。小程序端運(yùn)行在小程序中,讀寫數(shù)據(jù)庫受權(quán)限控制限制,管理端運(yùn)行在云函數(shù)上,擁有所有讀寫數(shù)據(jù)庫的權(quán)限。云控制臺(tái)的權(quán)限同管理端,擁有所有權(quán)限。小程序端操作數(shù)據(jù)庫應(yīng)有嚴(yán)格的安全規(guī)則限制。
我們提供了兩種權(quán)限控制方案,第一種是初期提供的基礎(chǔ)的四種簡(jiǎn)易權(quán)限設(shè)置,第二種是靈活的、可自定義的權(quán)限控制,即數(shù)據(jù)庫安全規(guī)則。
以下幾種權(quán)限配置是基礎(chǔ)的簡(jiǎn)易權(quán)限配置,如果需要靈活的自定義權(quán)限配置,請(qǐng)使用數(shù)據(jù)庫安全規(guī)則
每個(gè)集合可以擁有一種權(quán)限配置,權(quán)限配置的規(guī)則是作用在集合的每個(gè)記錄上的。出于易用性和安全性的考慮,云開發(fā)為云數(shù)據(jù)庫做了小程序深度整合,在小程序中創(chuàng)建的每個(gè)數(shù)據(jù)庫記錄都會(huì)帶有該記錄創(chuàng)建者(即小程序用戶)的信息,以 _openid 字段保存用戶的 openid 在每個(gè)相應(yīng)用戶創(chuàng)建的記錄中。因此,權(quán)限控制也相應(yīng)圍繞著一個(gè)用戶是否應(yīng)該擁有權(quán)限操作其他用戶創(chuàng)建的數(shù)據(jù)展開。
以下按照權(quán)限級(jí)別從寬到緊排列如下:
簡(jiǎn)而言之,管理端始終擁有讀寫所有數(shù)據(jù)的權(quán)限,小程序端始終不能寫他人創(chuàng)建的數(shù)據(jù),小程序端的記錄的讀寫權(quán)限其實(shí)分為了 “所有人可讀,只有創(chuàng)建者可寫“、”僅創(chuàng)建者可讀寫“、”所有人可讀,僅管理端可寫“、”所有人不可讀,僅管理端可讀寫“。
對(duì)一個(gè)用戶來說,不同模式在小程序端和管理端的權(quán)限表現(xiàn)如下:
模式 | 小程序端 讀自己創(chuàng)建的數(shù)據(jù) | 小程序端 寫自己創(chuàng)建的數(shù)據(jù) | 小程序端 讀他人創(chuàng)建的數(shù)據(jù) | 小程序端 寫他人創(chuàng)建的數(shù)據(jù) | 管理端 讀寫任意數(shù)據(jù) |
---|---|---|---|---|---|
僅創(chuàng)建者可寫,所有人可讀 | √ | √ | √ | × | √ |
僅創(chuàng)建者可讀寫 | √ | √ | × | × | √ |
僅管理端可寫,所有人可讀 | √ | × | √ | × | √ |
僅管理端可讀寫:該數(shù)據(jù)只有管理端可讀寫 | × | × | × | × | √ |
在設(shè)置集合權(quán)限時(shí)應(yīng)謹(jǐn)慎設(shè)置,防止出現(xiàn)越權(quán)操作。
開發(fā)者工具 1.02.1911252 起支持配置
安全規(guī)則是一個(gè)讓開發(fā)者可以靈活地自定義前端數(shù)據(jù)庫讀寫權(quán)限的能力,通過配置安全規(guī)則,開發(fā)者可以精細(xì)化的控制集合中所有記錄的讀、寫權(quán)限,自動(dòng)拒絕不符合安全規(guī)則的前端數(shù)據(jù)庫請(qǐng)求,保障數(shù)據(jù)安全。使用安全規(guī)則,你將獲得以下能力:
同時(shí)隨著安全規(guī)則的開放,前端批量更新(where.update、where.remove)也隨之開放(基礎(chǔ)庫 2.9.4 起),開發(fā)者在進(jìn)行批量更新時(shí)應(yīng)搭配安全規(guī)則使用以保障數(shù)據(jù)安全。
安全規(guī)則要求前端發(fā)起的查詢條件必須是安全規(guī)則的子集,否則拒絕訪問。比如定義一個(gè)讀寫訪問規(guī)則是 auth.openid == doc._openid,則表示訪問時(shí)的查詢條件(doc)的 openid 必須等于當(dāng)前用戶的 openid (由系統(tǒng)賦值的不可篡改的 auth.openid 給出),如果查詢條件沒有包含這項(xiàng),則表示嘗試越權(quán)訪問 _openid 字段不等于自身的記錄,會(huì)被后臺(tái)拒絕訪問。
除了安全規(guī)則外,云開發(fā)數(shù)據(jù)庫提供了四種基礎(chǔ)權(quán)限配置,適用于簡(jiǎn)單的前端訪問控制,只支持 4 種預(yù)設(shè)的規(guī)則(對(duì)集合中的每條數(shù)據(jù)記錄):
但基礎(chǔ)的設(shè)置給前端的訪問權(quán)限控制是有一定局限性、同時(shí)會(huì)帶來一些容易疑惑的、需要深入理解的系統(tǒng)默認(rèn)行為:
因此,我們建議開發(fā)者使用新推出的數(shù)據(jù)庫安全規(guī)則取代基礎(chǔ)權(quán)限配置,可以讓數(shù)據(jù)庫訪問的行為更加明確,同時(shí)取消需要深入理解的系統(tǒng)默認(rèn)行為,讓數(shù)據(jù)庫權(quán)限控制更加簡(jiǎn)單明確。
新的安全規(guī)則與舊的四種基礎(chǔ)權(quán)限配置的對(duì)應(yīng)關(guān)系如下,我們?cè)诖讼冉o出樣例,下方再給出具體的安全規(guī)則使用指南:
新自定義安全規(guī)則與舊權(quán)限配置的對(duì)應(yīng)關(guān)系
所有用戶可讀,僅創(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
}
我們可以在控制臺(tái)對(duì)各個(gè)集合分別配置安全規(guī)則,入口在集合權(quán)限配置頁,在基礎(chǔ)的四種權(quán)限配置外還提供了 “自定義規(guī)則” 的選項(xiàng)。
每個(gè)集合都有獨(dú)立的安全規(guī)則配置,配置的格式為 json,比如如下一個(gè)在某集合上的安全規(guī)則配置:
{
"read": "true",
"write": "auth.openid === doc._openid"
}
這配置其實(shí)就對(duì)應(yīng)著已有的 "所有用戶可讀,僅創(chuàng)建者可寫" 這一權(quán)限配置。配置的 key 表示操作類型,value 是一個(gè)表達(dá)式,表示需要滿足什么條件才允許相應(yīng)的操作類型。當(dāng)表達(dá)式解析為 true 時(shí)即代表相應(yīng)類型的操作符合安全規(guī)則。
支持配置的操作類型如下:
操作類型 | 說明 | 默認(rèn)值 |
---|---|---|
read | 讀 | false |
write | 寫,可以細(xì)分為 create、update、delete | false |
create | 新建 | 無 |
update | 更新 | 無 |
delete | 刪除 | 無 |
規(guī)則表達(dá)式是類 js 的表達(dá)式,支持部分表達(dá)式,內(nèi)置全局變量、全局函數(shù)。
變量 | 類型 | 說明 |
---|---|---|
auth | object | 用戶登錄信息,auth.openid 是用戶 openid |
doc | object | 記錄內(nèi)容,用于匹配記錄內(nèi)容/查詢條件 |
now | number | 當(dāng)前時(shí)間的時(shí)間戳 |
運(yùn)算符 | 說明 | 示例 | 示例解釋(集合查詢) |
---|---|---|---|
== | 等于 | 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']中的一個(gè) |
!(xx in []) | 不存在在集合中,使用 in 的方式描述 !(a in [1,2,3]) | !(auth.openid in ['zzz','aaa']) | 用戶的 openid 不是['zzz','aaa']中的任何一個(gè) |
&& | 與 | auth.openid == 'zzz' && doc.age>10 | 用戶的 openid 為 zzz 并且查詢條件的 age 屬性大于 10 |
|| | 或 | auth.openid == 'zzz' || doc.age>10 | 用戶的 openid 為 zzz 或者查詢條件的 age 屬性大于 10 |
. | 對(duì)象元素訪問符 | auth.openid | 用戶的 openid |
[] | 數(shù)組訪問符屬性 | doc.favorites[0] == 'zzz' | 查詢條件的 favorites 數(shù)組字段的第一項(xiàng)的值等于 zzz |
get:獲取指定記錄
get 函數(shù),用于在安全規(guī)則中獲取其記錄來參與到安全規(guī)則的匹配中,函數(shù)的參數(shù)格式是 `database.集合名.記錄id`,可以接收變量,值可以通過多種計(jì)算方式得到,例如使用字符串模版進(jìn)行拼接(database.${doc.collction}.${doc.\_id})。
如果有對(duì)應(yīng)對(duì)象,則函數(shù)返回記錄的內(nèi)容,否則返回空。
示例:
{
"read": "true",
"delete": "get(`database.user.${id}`).isManager"
}
get 函數(shù)有以下限制條件:
讀操作觸發(fā)與配額消耗說明:
get 函數(shù)的執(zhí)行會(huì)計(jì)入數(shù)據(jù)庫請(qǐng)求數(shù),同樣受數(shù)據(jù)庫配額限制。在未使用變量的情況下,每個(gè) get 會(huì)產(chǎn)生一次讀操作,在使用變量時(shí),對(duì)每個(gè)變量值會(huì)產(chǎn)生一次 get 讀操作。例如:
假設(shè)某集合 shop 上有如下規(guī)則:
{
"read": "auth.openid == get(`database.shop.${doc._id}`).owner",
"write": false
}
在執(zhí)行如下查詢語句時(shí)會(huì)產(chǎn)生 5 次讀取。
db.collection('shop').where(_.or([{_id:1},{_id:2},{_id:3},{_id:4},{_id:5}])).get()
對(duì)于查詢或更新操作,輸入的查詢條件必須是安全規(guī)則的子集。系統(tǒng)不會(huì)去實(shí)際取數(shù)據(jù),而會(huì)判斷輸入的查詢條件是否是安全規(guī)則的子集,如果不是,則代表正在嘗試訪問沒有權(quán)限訪問的數(shù)據(jù),會(huì)直接拒絕操作。
可能生效的操作類型包括 read、write、update、delete
示例:
// 集合 test 的安全規(guī)則配置限制只能查詢 age > 10 的記錄
{
"read": "doc.age > 10"
}
// 符合安全規(guī)則
const res = await db.collection('test').where({
age: _.gt(10)
}).get()
// 不符合安全規(guī)則
const res = db.collection('test').where({
age: _.gt(8)
}).get()
對(duì)于 create,則會(huì)校驗(yàn)寫入的數(shù)據(jù)是否符合安全規(guī)則。
在查詢時(shí),當(dāng)前用戶 openid 是常用的變量,在新的安全規(guī)則體系下,要求顯式傳入 openid,因此為了方便開發(fā)者、讓開發(fā)者無需每次先通過云函數(shù)獲取用戶 openid,我們規(guī)定查詢條件中可使用一個(gè)字符串常量 {openid},在后臺(tái)中發(fā)現(xiàn)該字符串時(shí)會(huì)自動(dòng)替換為小程序用戶的 openid,如假設(shè)有安全規(guī)則:
{
"read": "doc.publisher == auth.openid"
}
則發(fā)起讀請(qǐng)求時(shí)可以使用 {openid} 常量,效果等同于顯示傳入當(dāng)前用戶的實(shí)際 openid :
db.collection('test').where({
publisher: '{openid}'
}).get()
未登錄模式即無登錄態(tài)的模式,在未登錄模式中,auth 為空,開發(fā)者可以以此判斷是未登錄用戶的訪問。未登錄模式的場(chǎng)景有如:
由于安全規(guī)則要求查詢條件是安全規(guī)則的子集,同時(shí)摒棄了舊有權(quán)限配置的隱式默認(rèn)行為,因此啟動(dòng)安全規(guī)則需要開發(fā)者注意以下升級(jí)/兼容處理:
1. doc 操作需轉(zhuǎn)為 where 操作
因 doc 操作(doc.get, doc.set 等)是僅指定 _id 進(jìn)行的操作,因此其查詢條件大部分情況下并不會(huì)滿足安全規(guī)則(除非在 "read": true 下進(jìn)行讀操作或在 "write": true 的情況下進(jìn)行寫操作),因此需要轉(zhuǎn)換為等價(jià)的、查詢條件包含安全規(guī)則或是其子集的形式。例:
假設(shè)在集合 todo 上有以下權(quán)限規(guī)則:
{
"read": "doc._openid == auth.openid",
"write": "doc._openid == auth.openid"
}
舊權(quán)限配置可以通過 db.collection('todo').doc('x').get() 獲取記錄內(nèi)容,新安全規(guī)則需要改為:
db.collection('todo').where({
_id: 'x',
_openid: '{openid}',
})
doc.update, doc.remove 同理,注意 doc.set 無法使用,需要用 doc.update 替代。
2. 從舊權(quán)限配置升級(jí)后,查詢更新語句都需明確指定 openid
因升級(jí)前查詢條件可以不傳 _openid,而升級(jí)后要求顯示傳入以保證查詢條件符合安全規(guī)則,因此所有查詢條件均需傳入 openid,還是以上一節(jié)中的安全規(guī)則示例為例,對(duì)舊權(quán)限配置中的如下查詢語句:
db.collection('todo').where({
progress: _.lt(50)
}).get()
需要改為:
db.collection('todo').where({
_openid: '{openid}',
progress: _.lt(50)
}).get()
在開放安全規(guī)則后,where.update 和 where.remove 也在小程序端開放了,可以進(jìn)行符合安全規(guī)則的批量更新,如:
db.collection('todo').where({
_openid: '{openid}',
category: 'sport'
}).update({
progress: _.inc(10)
})
以下給出三個(gè)簡(jiǎn)易示例:群聊、信息流評(píng)論、商品訂單管理。
user
{
_id: string,
_openid: string,
name: string,
}
room
{
_id: string,
owner: string, // 群主 openid
name: string, // 群名
members: string[], // 成員 openid 列表
}
message
{
_id: string,
room: string, // 房間 id
sender: string, // 發(fā)送者 openid
content: string, // 消息內(nèi)容
time: Date, // 發(fā)送時(shí)間
withdrawn: boolean, // 是否已撤回
}
user 權(quán)限規(guī)則
{
"read": "doc._openid == auth.openid", // 私有讀
"write": "doc._openid == auth.openid", // 僅能修改自己的信息
}
room 權(quán)限規(guī)則
{
"read": "auth.openid in get('database.room.${doc._id}').members", // 僅群成員可以讀群信息
// 要求管理房間的寫操作不能在前端:
// - 原子:建群時(shí)需保證room集合的members和各個(gè)成員的rooms都寫入
// - 權(quán)限:僅群主能修改群信息
// - 權(quán)限:僅群成員可以拉新成員進(jìn)群
// - 權(quán)限:僅群主可以踢人
"write": false
}
message 權(quán)限規(guī)則
{
// 僅能讀取自己所在房間的聊天消息,且不允許讀取已撤回的消息
"read": "auth.openid in get('database.room.${doc.room}').members && doc.withdrawn == false",
// 只能在云函數(shù)寫:
// - 僅能在自己所在的房間發(fā)消息
// - 只能修改自己發(fā)送的消息
// - 不能刪除自己發(fā)送的消息(只能撤回)
"create": "auth.openid in get('database.room.${doc.room}').members",
"update": "auth.openid == doc.sender",
"delete": false
}
監(jiān)聽自己所在的某個(gè)房間的某個(gè)時(shí)間點(diǎn)之后的新消息(就是監(jiān)聽已接收的某個(gè)消息后的新消息):
wx.cloud.init({
env: '環(huán)境 ID',
})
const db = wx.cloud.database()
const _ = db.command
const watcher = db.collection('message').where({
room: '房間 id',
time: _.gt(new Date('2019-09-01 10:00')),
}).watch({
onChange: snapshot => {
console.log(`新事件`, snapshot)
},
onError: err => {
console.error(`監(jiān)聽錯(cuò)誤`, err)
}
})
user: 用戶信息集合,以用戶 openid 為 id
{
_id: string, // openid
_openid: string,
name: string,
isManager: boolean, // 管理員標(biāo)記位
}
article: 文章集合
{
_id: string,
publisher: string, // 發(fā)布者 openid
content: string, // 內(nèi)容
}
comment: 評(píng)論集合
{
_id: string,
commenter: string, // 評(píng)論者 openid
articleId: string, // 被評(píng)論的文章 id
content: string, // 評(píng)論內(nèi)容
}
article 安全規(guī)則
{
"read": true, // 公有讀
"create": "doc.publisher == auth.openid", // 都可以發(fā)文章,但對(duì)數(shù)據(jù)一致性校驗(yàn),要求 publisher 為發(fā)布者 openid
"update": "doc.publisher == auth.openid || get('database.user.${auth.openid}').isManager", // 僅發(fā)布者或管理員可以更新
"delete": "doc.publisher == auth.openid || get('database.user.${auth.openid}').isManager", // 僅發(fā)布者或管理員可以刪除
}
comment 安全規(guī)則
{
"read": true, // 公有讀
"create": "doc.commenter == auth.openid", // 都可以發(fā)評(píng)論,但對(duì)數(shù)據(jù)一致性校驗(yàn),要求 publisher 為發(fā)布者 openid
"update": "doc.commenter == auth.openid || get('database.user.${auth.openid}').isManager", // 僅發(fā)布者或管理員可以更新
"delete": "doc.commenter == auth.openid || get('database.user.${auth.openid}').isManager", // 僅發(fā)布者或管理員可以刪除
}
創(chuàng)建一條評(píng)論:
wx.cloud.init({
env: '環(huán)境 ID',
})
const db = wx.cloud.database()
const _ = db.command
const result = await db.collection('comment').add({
data: {
commenter: '{openid}', // 用 {openid} 變量,后臺(tái)會(huì)自動(dòng)替換為當(dāng)前用戶 openid
articleId: '文章 ID',
content: '評(píng)論內(nèi)容',
},
})
console.log('創(chuàng)建結(jié)果', result)
假設(shè)需要構(gòu)建一個(gè)簡(jiǎn)易商品管理系統(tǒng),有商店信息,一個(gè)商店對(duì)應(yīng)多個(gè)商品、多個(gè)訂單,商品信息公開可查,只有商店所有者或管理員可以查看自己商店的訂單信息。
shop 商店集合
{
_id: string,
name: string, // 商店名
location: GeoPoint, // 商店地理位置
owner: string, // 商店擁有者 openid
managers: string[], // 商店管理員 openid 列表
}
item 商品集合
{
_id: string,
shopId: string, // 所在商店
name: string, // 商品名
price: number, // 價(jià)格
stock: number, // 庫存
}
order 訂單集合
{
_id: string,
shopId: string, // 下單的商店
itemId: string, // 下單的商品
price: number, // 成交價(jià)格
amount: number, // 成交數(shù)量
status: string, // 狀態(tài)
createTime: Date, // 創(chuàng)建時(shí)間
updateTime: Date, // 更新時(shí)間
}
shop 安全規(guī)則:
{
"read": true, // 公有讀
"write": false, // 僅云函數(shù)端寫
}
item 安全規(guī)則:
{
// 公有讀
"read": true,
// 僅商店所有者或管理員可寫
"write": "auth.openid == get(`database.shop.${doc.shopId}`).owner || auth.openid in get(`database.shop.${doc.shopId}`).managers",
}
order 安全規(guī)則:
{
// 僅商店所有者或管理員可讀寫
"read": "auth.openid == get(`database.shop.${doc.shopId}`).owner || auth.openid in get(`database.shop.${doc.shopId}`).managers",
"write": "auth.openid == get(`database.shop.${doc.shopId}`).owner || auth.openid in get(`database.shop.${doc.shopId}`).managers",
// 僅云函數(shù)端可刪除訂單記錄
"delete": false,
}
監(jiān)聽自己所在商店的新訂單動(dòng)態(tài):
wx.cloud.init({
env: '環(huán)境 ID',
})
const db = wx.cloud.database()
const _ = db.command
const watcher = db.collection('order').where({
shopId: '商店 id',
createTime: _.gt(new Date()),
}).watch({
onChange: snapshot => {
console.log(`新事件`, snapshot)
},
onError: err => {
console.error(`監(jiān)聽錯(cuò)誤`, err)
}
})
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: