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