PHP-Casbin

PHP-Casbin

基礎(chǔ)知識

概述

Casbin是一個強大的覆山、高效的開源訪問控制框架,其權(quán)限管理機制支持多種訪問控制模型除抛。

Casbin是什么丧失?

Casbin可以做到:

  1. 支持自定義請求的格式,默認的請求格式為{subject, object, action}砂代。
  2. 具有訪問控制模型model和策略policy兩個核心概念蹋订。
  3. 支持RBAC中的多層角色繼承,不止主體可以有角色刻伊,資源也可以具有角色辅辩。
  4. 支持超級用戶,如 rootAdministrator娃圆,超級用戶可以不受授權(quán)策略的約束訪問任意資源。
  5. 支持多種內(nèi)置的操作符蛾茉,如 keyMatch讼呢,方便對路徑式的資源進行管理,如 /foo/bar 可以映射到 /foo*

Casbin不能做到:

  1. 身份認證 authentication(即驗證用戶的用戶名谦炬、密碼)悦屏,casbin只負責(zé)訪問控制。應(yīng)該有其他專門的組件負責(zé)身份認證键思,然后由casbin進行訪問控制础爬,二者是相互配合的關(guān)系。
  2. 管理用戶列表或角色列表吼鳞。 Casbin 認為由項目自身來管理用戶看蚜、角色列表更為合適, 用戶通常有他們的密碼赔桌,但是 Casbin 的設(shè)計思想并不是把它作為一個存儲密碼的容器供炎。 而是存儲RBAC方案中用戶和角色之間的映射關(guān)系。

安裝

composer require casbin/casbin

開始使用

require_once './vendor/autoload.php';

use Casbin\Enforcer;

$e = new Enforcer("path/to/model.conf", "path/to/policy.csv");

你可以在初始化Enforcer實例時疾党,使用數(shù)據(jù)庫代替文件音诫,相關(guān)說明,后面將會提到

進行訪問控制

$sub = "alice"; // 角色名
$obj = "data1"; // 訪問的資源
$act = "read"; // 訪問的權(quán)限

// 驗證該角色是否有訪問某資源的權(quán)限
if ($e->enforce($sub, $obj, $act) === true) {
    // 允許訪問
} else {
    // 禁止訪問
}

Casbin也提供了API用于權(quán)限管理雪位,例如:你可以獲取分配給某個角色的所有權(quán)限

$roles = $e->getRolesForUser("alice");

查看 Management APIRBAC API 獲取更多的用法

請參考測試用例來了解更多的用法

工作原理

在 Casbin 中, 訪問控制模型被抽象為基于 PERM (Policy, Effect, Request, Matcher) 的一個文件竭钝。 因此,切換或升級項目的授權(quán)機制與修改配置一樣簡單雹洗。 您可以通過組合可用的模型來定制您自己的訪問控制模型香罐。 例如,您可以在一個model中獲得RBAC角色和ABAC屬性时肿,并共享一組policy規(guī)則穴吹。

Policy:策略 Effect:作用范圍 Request:請求 Matcher:匹配器

Casbin中最基本、最簡單的model是ACL嗜侮。ACL中的model CONF為:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

ACL model的示例policy如下:

p, alice, data1, read
p, bob, data2, write

這表示:

  • alice可以讀取data1
  • bob可以編寫data2

對于過長的單行配置港令,您也可以通過在結(jié)尾處添加“\”進行斷行:

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj \ 
  && r.act == p.act

此外啥容,對于 ABAC,您在可以在 Casbin golang 版本中嘗試下面的 (jCasbin 和 Node-Casbin 尚不支持)操作:

# 匹配器
[matchers]
m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')

但是你應(yīng)確保數(shù)組的長度大于 1顷霹,否則的話將會導(dǎo)致 panic 咪惠。

對于更多操作,你可以查看govaluate淋淀。

Model

支持的Models

  1. ACL (Access Control List, 訪問控制列表)
  2. 具有的超級用戶ACL
  3. 沒有用戶的 ACL: 對于沒有身份驗證或用戶登錄的系統(tǒng)尤其有用遥昧。
  4. 沒有資源的 ACL: 某些場景可能只針對資源的類型, 而不是單個資源, 諸如 write-article, read-log等權(quán)限。 它不控制對特定文章或日志的訪問朵纷。
  5. RBAC (基于角色的訪問控制)
  6. 支持資源角色的RBAC: 用戶和資源可以同時具有角色 (或組)炭臭。
  7. 支持域/租戶的RBAC: 用戶可以為不同的域/租戶設(shè)置不同的角色集。
  8. ABAC (基于屬性的訪問控制): 支持利用resource.Owner這種語法糖獲取元素的屬性袍辞。
  9. RESTful: 支持路徑, 如 /res/*, /res/: id 和 HTTP 方法, 如 GET, POST, PUT, DELETE鞋仍。
  10. 拒絕優(yōu)先: 支持允許和拒絕授權(quán), 拒絕優(yōu)先于允許。
  11. 優(yōu)先級: 策略規(guī)則按照先后次序確定優(yōu)先級搅吁,類似于防火墻規(guī)則威创。

例子

訪問控制模型 Model 文件 Policy 文件
ACL basic_model.conf basic_policy.csv
具有超級用戶的ACL basic_model_with_root.conf basic_policy.csv
沒有用戶的ACL basic_model_without_users.conf basic_policy_without_users.csv
沒有資源的ACL basic_model_without_resources.conf basic_policy_without_resources.csv
RBAC rbac_model.conf rbac_policy.csv
支持資源角色的RBAC rbac_model_with_resource_roles.conf rbac_policy_with_resource_roles.csv
支持域/租戶的RBAC rbac_model_with_domains.conf rbac_policy_with_domains.csv
ABAC abac_model.conf
RESTful keymatch_model.conf keymatch_policy.csv
拒絕優(yōu)先 rbac_model_with_deny.conf rbac_policy_with_deny.csv
優(yōu)先級 priority_model.conf priority_policy.csv

Model語法

  • Model CONF 至少應(yīng)包含四個部分: [request_definition], [policy_definition], [policy_effect], [matchers]
  • 如果 model 使用 RBAC, 還需要添加[role_definition]部分谎懦。
  • 一個Model CONF可以包含注釋肚豺。注釋以#開頭

request_definition:請求定義 policy_definition:策略定義 policy_effect:策略作用范圍 matchers:匹配器 role_definition:角色定義

Request定義

[request_definition] 部分用于request的定義,它明確了 $e->enforce($sub, $obj, $act) 函數(shù)中參數(shù)的含義界拦。

# 請求定義
[request_definition]
r = sub, obj, act

sub, obj, act 表示經(jīng)典三元組: 訪問實體 (Subject)吸申,訪問資源 (Object) 和訪問方法 (Action)。 但是, 你可以自定義你自己的請求表單, 如果不需要指定特定資源享甸,則可以這樣定義 sub呛谜、act ,或者如果有兩個訪問實體, 則為 sub枪萄、sub2隐岛、obj、act瓷翻。

Policy定義

[policy_definition] 部分是對policy的定義聚凹,以下文的 model 配置為例:

# 策略定義
[policy_definition]
p = sub, obj, act
p2 = sub, act

這些是我們對policy規(guī)則的具體描述

p, alice, data1, read
p2, bob, write-all-objects

policy部分的每一行稱之為一個策略規(guī)則, 每條策略規(guī)則通常以形如p, p2policy type開頭齐帚。 如果存在多個policy定義响禽,那么我們會根據(jù)前文提到的policy type與具體的某條定義匹配偿洁。 上面的policy的綁定關(guān)系將會在matcher中使用顷牌, 羅列如下:

(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)

注1: 當前只支持形如 p的單個policy定義己单, 形如p2 類型的尚未支持。 通常情況下, 用戶無需使用多個 policy 定義剪菱, 如果您有其他情形的policy定義訴求摩瞎,請在 https://github.com/casbin/casbin/issues/new 提出issue告知我們拴签。

注2: policy定義中的元素始終被視為字符串(string)對待, 如果您對此有疑問旗们,請移步https://github.com/casbin/casbin/issues/113

Policy effect定義

[policy_effect] 部分是對policy生效范圍的定義蚓哩, 原語定義了當多個policy rule同時匹配訪問請求request時,該如何對多個決策結(jié)果進行集成以實現(xiàn)統(tǒng)一決策。 以下示例展示了一個只有一條規(guī)則生效上渴,其余都被拒絕的情況:

# 策略生效范圍
[policy_effect]
e = some(where (p.eft == allow))

該Effect原語表示如果存在任意一個決策結(jié)果為allow的匹配規(guī)則岸梨,則最終決策結(jié)果為allow,即allow-override稠氮。 其中p.eft 表示策略規(guī)則的決策結(jié)果曹阔,可以為allow 或者deny,當不指定規(guī)則的決策結(jié)果時,取默認值allow 隔披。 通常情況下赃份,policy的p.eft默認為allow, 因此前面例子中都使用了這個默認值锹锰。

這是另一個policy effect的例子:

# 策略生效范圍
[policy_effect]
e = !some(where (p.eft == deny))

該Effect原語表示不存在任何決策結(jié)果為deny的匹配規(guī)則,則最終決策結(jié)果為allow 漓库,即deny-override恃慧。 some 量詞判斷是否存在一條策略規(guī)則滿足匹配器。 any 量詞則判斷是否所有的策略規(guī)則都滿足匹配器 (此處未使用)渺蒿。

policy effect還可以利用邏輯運算符進行連接:

# 策略生效范圍
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

該Effect原語表示當至少存在一個決策結(jié)果為allow的匹配規(guī)則痢士,且不存在決策結(jié)果為deny的匹配規(guī)則時,則最終決策結(jié)果為allow茂装。 這時allow授權(quán)和deny授權(quán)同時存在怠蹂,但是deny優(yōu)先。

Matchers

[matchers] 原語定義了策略規(guī)則如何與訪問請求進行匹配的匹配器少态,其本質(zhì)上是布爾表達式城侧,可以理解為Request、Policy等原語定義了關(guān)于策略和請求的變量,然后將這些變量代入Matcher原語中求值,從而進行策略決策彼妻。

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

這是一個簡單的例子嫌佑,該Matcher原語表示,訪問請求request中的subject、object侨歉、action三元組應(yīng)與策略規(guī)則policy rule中的subject屋摇、object、action三元組分別對應(yīng)相同幽邓。

Matcher原語支持+炮温、 -、 *牵舵、 /等算數(shù)運算符柒啤,==,倦挂、!=、 >白修、 <等關(guān)系運算符以及&& (與)妒峦、|| (或)、 ! (非)等邏輯運算符兵睛。

: 雖然可以像其他原語一樣的編寫多個類似于 m1, m2 的matcher肯骇, 但是當前我們只支持一個有效的 matcher m。 通常情況下祖很,您可以在一個matcher中使用上文提到的邏輯運算符來實現(xiàn)復(fù)雜的邏輯判斷笛丙, 因而我們認為目前不需要支持多個matcher。 如果您對此有疑問假颇,請告知我們(https://github.com/casbin/casbin/issues)胚鸯。

matcher中的函數(shù)

matcher的強大與靈活之處在于您甚至可以在matcher中定義函數(shù),這些函數(shù)可以是內(nèi)置函數(shù)或自定義的函數(shù)笨鸡。當前支持的內(nèi)置函數(shù)如下:

函數(shù) 釋義 示例
keyMatch(arg1, arg2) 參數(shù) arg1 是一個 URL 路徑姜钳,例如 /alice_data/resource1,參數(shù) arg2 可以是URL路徑或者是一個 * 模式形耗,例如 /alice_data/*哥桥。此函數(shù)返回 arg1是否與 arg2 匹配。 keymatch_model.conf/keymatch_policy.csv
keyMatch2(arg1, arg2) 參數(shù) arg1 是一個 URL 路徑激涤,例如 /alice_data/resource1拟糕,參數(shù) arg2 可以是 URL 路徑或者是一個 : 模式,例如/alice_data/:resource倦踢。此函數(shù)返回 arg1 是否與 arg2 匹配送滞。 keymatch2_model.conf/keymatch2_policy.csv
regexMatch(arg1, arg2) arg1 可以是任何字符串。arg2 是一個正則表達式辱挥。它返回 arg1 是否匹配 arg2犁嗅。 keymatch_model.conf/keymatch_policy.csv
ipMatch(arg1, arg2) arg1 是一個 IP 地址, 如 192.168.2.123。arg2 可以是 IP 地址或 CIDR, 如 192.168.2. 0/24晤碘。它返回 arg1 是否匹配 arg2愧哟。 ipmatch_model.conf/ipmatch_policy.csv

如何添加自定義函數(shù)(GO語言示例)

首先準備好一個有幾個參數(shù)和一個布爾值返回值的函數(shù):

func KeyMatch(key1 string, key2 string) bool {
    i := strings.Index(key2, "*")
    if i == -1 {
        return key1 == key2
    }

    if len(key1) > i {
        return key1[:i] == key2[:i]
    }
    return key1 == key2[:i]
}

然后用 interface{} 類型包裝此函數(shù):

func KeyMatchFunc(args ...interface{}) (interface{}, error) {
    name1 := args[0].(string)
    name2 := args[1].(string)

    return (bool)(KeyMatch(name1, name2)), nil
}

最后, 將該函數(shù)注冊到 Casbin enforcer:

e.AddFunction("my_func", KeyMatchFunc)

現(xiàn)在, 您可以像下面這樣使用 model 中的函數(shù):

# 匹配器
[matchers]
m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act

RBAC

角色定義

[role_definition] 是RBAC角色繼承關(guān)系的定義。 Casbin 支持 RBAC 系統(tǒng)的多個實例, 例如, 用戶可以具有角色及其繼承關(guān)系, 資源也可以具有角色及其繼承關(guān)系哼蛆。 這兩個 RBAC 系統(tǒng)不會互相干擾蕊梧。

此部分是可選的。 如果在模型中不使用 RBAC 角色, 則省略此部分腮介。

# 角色定義
[role_definition]
g = _, _
g2 = _, _

上述角色定義表明, g 是一個 RBAC系統(tǒng), g2 是另一個 RBAC 系統(tǒng)肥矢。 _, _表示角色繼承關(guān)系的前項和后項,即前項繼承后項角色的權(quán)限。 一般來講甘改,如果您需要進行角色和用戶的綁定旅东,直接使用g 即可。 當您需要表示角色(或者組)與用戶和資源的綁定關(guān)系時十艾,可以使用gg2 這樣的表現(xiàn)形式抵代。 請參見 rbac_modelrbac_model_with_resource_roles 的示例。

在Casbin里忘嫉,我們以policy表示中實際的用戶角色映射關(guān)系 (或是資源-角色映射關(guān)系)荤牍,例如:

p, data2_admin, data2, read
g, alice, data2_admin

這意味著 alice 是角色 data2_admin的一個成員。 alice 在這里可以是用戶庆冕、資源或角色康吵。 Cabin 只是將其識別為一個字符串。

接下來在matcher中访递,應(yīng)該像下面的例子一樣檢查角色信息:

# 匹配器
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

它表示請求中的sub應(yīng)該在策略中定義了sub角色晦嵌。

有幾個注意事項:

  1. Casbin 只存儲用戶角色的映射關(guān)系。
  2. Cabin 沒有驗證用戶是否是有效的用戶拷姿,或者角色是一個有效的角色惭载。 這應(yīng)該通過認證來解決。
  3. RBAC 系統(tǒng)中的用戶名稱和角色名稱不應(yīng)相同响巢。因為Casbin將用戶名和角色識別為字符串描滔, 所以當前語境下Casbin無法得出這個字面量到底指代用戶 alice 還是角色 alice。 這時抵乓,使用明確的 role_alice 伴挚,問題便可迎刃而解靶衍。
  4. 假設(shè)A具有角色 B灾炭,B 具有角色 C,并且 A 有角色 C颅眶。 這種傳遞性在當前版本會造成死循環(huán)蜈出。

角色層次

Casbin 的 RBAC 支持 RBAC1 的角色層次結(jié)構(gòu)功能,如果 alice具有role1, role1具有role2涛酗,則 alice 也將擁有 role2 并繼承其權(quán)限铡原。

下面是一個稱為層次結(jié)構(gòu)級別的概念。 因此, 此示例的層次結(jié)構(gòu)級別為2商叹。 對于Casbin中的內(nèi)置角色管理器, 可以指定最大層次結(jié)構(gòu)級別燕刻。 默認值為10。 這意味著終端用戶 alice 只能繼承10個級別的角色剖笙。

// GO語言示例
// NewRoleManager重寫了默認角色管理RoleManger的構(gòu)造方法
func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager {
    rm := RoleManager{}
    rm.allRoles = &sync.Map{}
    rm.maxHierarchyLevel = maxHierarchyLevel
    rm.hasPattern = false

    return &rm
}

如何區(qū)分用戶和角色卵洗?

在RBAC中,Casbin不對用戶和角色進行區(qū)分弥咪。 它們都被視為字符串过蹂。 如果你只使用單層的RBAC模型(角色不會成為另一個角色的成員)十绑。 可以使用 e.GetAllSubjects() 獲取所有用戶,e.GetAllRoles() 獲取所有角色酷勺。 它們會為規(guī)則 g, u, r 分別列出所有的 ur本橙。

但在使用多層RBAC模型時(帶有角色繼承),你的應(yīng)用不會記錄一個名字(字符串)是用戶還是角色脆诉,或者用戶和角色有相同的名字甚亭。 可以給角色加上像 role::admin 的前綴再傳遞到Casbin中。 由此可以通過查看前綴來區(qū)分用戶和角色库说。

如何查詢隱性角色或權(quán)限狂鞋?

當用戶通過RBAC層次結(jié)構(gòu)繼承角色或權(quán)限,而不是直接在策略規(guī)則中分配它們時潜的,我們將這種類型的分配稱為 implicit骚揍。 要查詢這種隱式關(guān)系,需要使用以下兩個api: GetImplicitRolesForUser()以及 GetImplicitPermissionsForUser 替代GetRolesForUser() 以及 GetPermissionsForUser. 有關(guān)詳情啰挪,請參閱 this GitHub issue信不。

在 RBAC 中使用模式匹配

有時,您希望將具有特定模式的某些subjects(或objects)自動授予某個角色亡呵。 RBAC中的模式匹配函數(shù)可以幫助做到這一點抽活。 模式匹配函數(shù)與前一個函數(shù)共享相同的參數(shù)和返回值:matcher function

我們知道RBAC在matcher中通常表示為g(r.sub, p.sub)锰什。 然后我們將使用以下策略:

p, alice, book_group, read
g, /book/1, book_group
g, /book/2, book_group

所以 alice可以讀所有的book下硕,包括 book 1book 2。 但是可能有成千上萬個book汁胆,使用 g 策略規(guī)則將每一個book添加到book角色(或組)是非常單調(diào)乏味的梭姓。

但是使用模式匹配函數(shù),您可以只用一行代碼編寫策略:

g, /book/:id, book_group

Casbin會自動將 /book/1/book/2匹配成模式/book/:id嫩码。 您只需要向強制程序注冊該功能誉尖,如:

// GO語言示例
e.rm.(*defaultrolemanager.RoleManager).AddMatchingFunc("KeyMatch2", util.KeyMatch2)

您可以在這里看到完整的示例 :here

值得注意的是:

  1. 只有g種的第一參數(shù) (aka 用戶) 支持模式函數(shù)铸题。 您正在使用第三個參數(shù)(domain)铡恕,目前不支持。

角色管理器

角色管理器用于管理Casbin中的RBAC角色層次結(jié)構(gòu)(用戶角色映射)丢间。 角色管理器可以從Casbin策略規(guī)則或外部源(如LDAP探熔、Okta、Auth0烘挫、Azure AD等)檢索角色數(shù)據(jù)诀艰。 我們支持角色管理器的不同實現(xiàn)。 為了保持代碼輕量級,我們沒有把角色管理器代碼放在主庫中(默認的角色管理器除外)涡驮。 下面提供了Casbin角色管理器的完整列表暗甥。 歡迎任何第三方對角色manager進行新的貢獻,如果有請告知我們捉捅,我們將把它放在這個列表中:

角色管理器 作者 描述
Default Role Manager (built-in) Casbin 支持存儲在Casbin策略中的角色層次結(jié)構(gòu)
Session Role Manager EDOMO Systems 支持存儲在Casbin策略中的角色層次結(jié)構(gòu)撤防,以及基于時間范圍的會話
Okta Role Manager Casbin 支持存儲在Okta中的角色層次結(jié)構(gòu)
Auth0 Role Manager Casbin 支持存儲在 Auth0's Authorization Extension 授權(quán)擴展名中的角色層次結(jié)構(gòu)

對于開發(fā)人員:所有角色manager必須實現(xiàn) RoleManager接口。 Session Role Manager可以用作參考實現(xiàn)棒口。

RBAC with Domains

域租戶的角色定義

在Casbin中的RBAC角色可以是全局或是基于特定于域的寄月。 特定域的角色意味著當用戶處于不同的域/租戶群體時,用戶所表現(xiàn)的角色也不盡相同无牵。 這對于像云服務(wù)這樣的大型系統(tǒng)非常有用漾肮,因為用戶通常分屬于不同的租戶群體。

域/租戶的角色定義應(yīng)該類似于:

# 角色定義
[role_definition]
g = _, _, _

第三個 _ 表示域/租戶的名稱, 此部分不應(yīng)更改茎毁。

然后克懊,策略可以是:

p, admin, tenant1, data1, read
p, admin, tenant2, data2, read

g, alice, admin, tenant1
g, alice, user, tenant2

該實例表示tenant1的域內(nèi)角色admin 可以讀取data1alicetenant1域中具有admin角色七蜘,但在tenant2域中具有user角色橡卤, 所以alice可以有讀取data1的權(quán)限嵌灰。 同理秕脓,因為alice不是tenant2admin搂鲫,所以她訪問不了data2拣挪。

接下來在matcher中,應(yīng)該像下面的例子一樣檢查角色信息:

# 匹配器
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act

更多示例參見: rbac_with_domains_model.conf园骆。

ABAC

什么是ABAC模式巍耗?

ABAC是 基于屬性的訪問控制亲族,可以使用主體可缚、客體或動作的屬性帘靡,而不是字符串本身來控制訪問。 您之前可能就已經(jīng)聽過 XACML 描姚,是一個復(fù)雜的 ABAC 訪問控制語言筒扒。 與XACML相比冰蘑,Casbin的ABAC非常簡單: 在ABAC中祠肥,可以使用struct(或基于編程語言的類實例) 而不是字符串來表示模型元素允跑。

例如,ABAC的官方實例如下:

# 請求定義
[request_definition]
r = sub, obj, act

# 政策定義
[policy_definition]
p = sub, obj, act

# 政策應(yīng)用范圍
[policy_effect]
e = some(where (p.eft == allow))

# 匹配器
[matchers]
m = r.sub == r.obj.Owner

我們使用 r.obj.所有者 代替 r.obj matcher。 在 Enforce() 函數(shù)中傳遞的 r.obj 函數(shù)是結(jié)構(gòu)或類實例,而不是字符串棉姐。 Casbin將使用映像來檢索 obj結(jié)構(gòu)或類中的成員變量。

這里是 r.obj construction 或 class 的定義:

// GO語言示例
type testResource struct {
    Name  string
    Owner string
}

如何使用ABAC弱睦?

簡單地說,要使用ABAC渊额,您需要做兩件事:

  1. 在模型匹配器中指定屬性况木。
  2. 將元素的結(jié)構(gòu)或類實例作為Casbin的Enforce() 的參數(shù)傳入。

Note:

  1. 目前旬迹,僅請求元素火惊,例如 r.such、[ r.obj奔垦,] r.action 等等支持ABAC的元素屹耐。 您不能在策略元素上使用它,比如p.sub椿猎,因為在Casbin的策略中沒有定義結(jié)構(gòu)或者類惶岭。
  2. 您可以在一個matcher中使用多個ABAC屬性,例如:m = r.sub.Domain == r.obj.Domain犯眠。

存儲

Model存儲

與 policy 不同按灶,model 只能加載,不能保存筐咧。 因為我們認為 model 不是動態(tài)組件鸯旁,不應(yīng)該在運行時進行修改,所以我們沒有實現(xiàn)一個 API 來將 model 保存到存儲中量蕊。

但是铺罢,好消息是,我們提供了三種等效的方法來靜態(tài)或動態(tài)地加載模型:

從 .CONF 文件中加載 model

當你向 Casbin 團隊尋求幫助時危融,他們會給你這個 Casbin 最常用的方法畏铆,此方法對于初學(xué)者來說很容易理解并且便于分享雷袋。

.CONF文件的內(nèi)容 examples/rbac_model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

接著你可以加載模型文件如下:

// GO語言示例
e := casbin.NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

從代碼加載 model

型可以從代碼中動態(tài)初始化吉殃,不需要使用 .CONF辞居。下面是RBAC模型的一個例子:

// GO語言示例
// 初始化model
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")

// 使用自己的 adapter 替換。
a := persist.NewFileAdapter("examples/rbac_policy.csv")

// 創(chuàng)建一個 enforcer蛋勺。
e := casbin.NewEnforcer(m, a)

從字符串加載的 model

或者您可以從多行字符串加載整個模型文本瓦灶。這種方法的優(yōu)點是您不需要維護模型文件。

// GO語言示例
// Initialize the model from a string.
text :=
`
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m := NewModel(text)

// Load the policy rules from the .CSV file adapter.
// Replace it with your adapter to avoid files.
a := persist.NewFileAdapter("examples/rbac_policy.csv")

// Create the enforcer.
e := casbin.NewEnforcer(m, a)

Policy存儲

在Casbin中抱完,策略存儲作為adapter實現(xiàn)贼陶。請參照:</docs/en/adapters>。

Policy Subset Loading

一些adapter支持過濾策略管理巧娱。 這意味著Casbin加載的策略是基于給定過濾器的存儲策略的子集碉怔。 當解析整個策略成為性能瓶頸時,這將會允許在大型多租戶環(huán)境中有效地執(zhí)行策略禁添。

要使用支持的adapter處理過濾后的策略撮胧,只需調(diào)用 LoadFilteredPolicy 方法。 過濾器參數(shù)的有效格式取決于所用的適配器老翘。 為了防止意外數(shù)據(jù)丟失芹啥,當策略已經(jīng)加載, SavePolicy 方法會被禁用铺峭。

例如墓怀,下面的代碼片段使用內(nèi)置的過濾文件adapter和帶有域的RBAC模型。 在本例中卫键,過濾器將策略限制為單個域傀履。 除 "domain1" 以外的任何域策略行被忽略:

// GO語言示例
import "github.com/casbin/casbin"

enforcer := casbin.NewEnforcer()

adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
enforcer.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)

filter := &fileadapter.Filter{
    P: []string{"", "domain1"},
    G: []string{"", "", "domain1"},
}
enforcer.LoadFilteredPolicy(filter)

// The loaded policy now only contains the entries pertaining to "domain1".

Extensions

Adapters

在Casbin中,策略存儲作為adapter(Casbin的中間件) 實現(xiàn)莉炉。 Casbin用戶可以使用adapte從存儲中加載策略規(guī)則 (aka LoadPolicy()) 或者將策略規(guī)則保存到其中 (aka SavePolicy())啤呼。 為了保持代碼輕量級,我們沒有把adapte代碼放在主庫中呢袱。

目前支持的adapter列表

Casbin角色管理器的完整列表如下所示官扣。 歡迎任何第三方對adapter進行新的貢獻,如果有請通知我們羞福,我們將把它放在這個列表中:)

適配器 類型 作者 自動保存 描述
File Adapter (built-in) File Casbin ? For .CSV (Comma-Separated Values) files
Database Adapter ORM Casbin ? MySQL, PostgreSQL, SQLite, Microsoft SQL Server are supported by techone/database
Zend Db Adapter ORM Casbin ? MySQL, PostgreSQL, SQLite, Oracle, IBM DB2, Microsoft SQL Server, Other PDO Driver are supported by zend-db
Doctrine DBAL Adapter(Recommend) ORM Casbin ? Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.
Medoo Adapter ORM Casbin ? Medoo is a lightweight PHP Database Framework to Accelerate Development, supports all SQL databases, including MySQL, MSSQL, SQLite, MariaDB, PostgreSQL, Sybase, Oracle and more.

這里有一些你需要知道的事情:

  1. 如果使用顯式或隱式adapter調(diào)用casbin.NewEnforcer()惕蹄,策略將自動加載。
  2. 可以調(diào)用e.LoadPolicy() 來從存儲中重新加載策略規(guī)則治专。
  3. 如果adapter不支持Auto-Save特性卖陵,則在添加或刪除策略時不能將策略規(guī)則自動保存回存儲器。 您可以手動調(diào)用SavePolicy() 來保存所有策略規(guī)則张峰。

例子

這里我門提供了幾個例子:

文件適配器 (內(nèi)置)

下面展示了如何對內(nèi)置的文件適配器進行初始化:

use Casbin\Enforcer;

$e = new Enforcer('examples/basic_model.conf', 'examples/basic_policy.csv');

同樣的:

use Casbin\Enforcer;
use Casbin\Persist\Adapters\FileAdapter;

$a = new FileAdapter('examples/basic_policy.csv');
$e = new Enforcer('examples/basic_model.conf', $a);

MySQL 適配器

下面展示了如何初始化一個MySQL適配器

// https://github.com/php-casbin/dbal-adapter

use Casbin\Enforcer;
use CasbinAdapter\DBAL\Adapter as DatabaseAdapter;

$config = [
    // driver的可選值:
    // pdo_mysql,pdo_sqlite,pdo_pgsql,pdo_oci (unstable),pdo_sqlsrv,pdo_sqlsrv,
    // mysqli,sqlanywhere,sqlsrv,ibm_db2 (unstable),drizzle_pdo_mysql
    'driver'     => 'pdo_mysql', 
    'host' => '127.0.0.1',
    'dbname' => 'test',
    'user' => 'root',
    'password' => '',
    'port' => '3306',
];

$a = DatabaseAdapter::newAdapter($config);
$e = new Enforcer('examples/basic_model.conf', $a);

使用自建的adapter

你可以使用自定義的適配器泪蔫,例如:

// GO語言示例
import (
    "github.com/casbin/casbin"
    "github.com/your-username/your-repo"
)

a := yourpackage.NewAdapter(params)
e := casbin.NewEnforcer("examples/basic_model.conf", a)

在運行時進行加載或保存配置信息

你也許希望重新加載模型,重新加載策略或者在初始化后保存策略

// GO語言示例
// 從模型配置文件中重新加載模型
e.LoadModel()

// 從文件或數(shù)據(jù)庫中重新加載策略
e.LoadPolicy()

// Save the current policy (usually after changed with Casbin API) back to file/database.
// 將當前策略保存到文件或數(shù)據(jù)庫(通常在使用Casbin API之后)
e.SavePolicy()

自動保存

有一個稱為適配器自動保存的功能喘批。當一個適配器支持自動保存功能時撩荣,就意味著它能夠支持從存儲器中添加一條策略或刪除一條策略铣揉。這與savePolicy()不同,后者將刪除存儲中的所有策略規(guī)則餐曹,并將所有策略規(guī)則從casbin Enforcer保存到存儲中逛拱。因此,當策略規(guī)則的數(shù)量很大時台猴,它可能會遇到性能問題朽合。

當適配器支持自動保存時,可以通過enforcer.enableautosave()函數(shù)切換此選項饱狂。默認情況下曹步,該選項處于啟用狀態(tài)(如果適配器支持它)。

需要注意的是:

  1. Auto-Save 特性是可選的休讳。 Adapter可以選擇是否實現(xiàn)它箭窜。
  2. Auto-Save 只在Casbin enforcer使用的adapter支持它時才有效。
  3. 查看上述adapter列表中的 AutoSave列衍腥,查看adapter是否支持 Auto-Save磺樱。

這里有一個關(guān)于自動保存的例子:

// GO語言示例
import (
    "github.com/casbin/casbin"
    "github.com/casbin/xorm-adapter"
    _ "github.com/go-sql-driver/mysql"
)

// By default, the AutoSave option is enabled for an enforcer.
a := xormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
e := casbin.NewEnforcer("examples/basic_model.conf", a)

// Disable the AutoSave option.
e.EnableAutoSave(false)

// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
// it doesn't affect the policy in the storage.
e.AddPolicy(...)
e.RemovePolicy(...)

// Enable the AutoSave option.
e.EnableAutoSave(true)

// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
// but also affects the policy in the storage.
e.AddPolicy(...)
e.RemovePolicy(...)

更多示例,請參考:https://github.com/casbin/xorm-adapter/blob/master/adapter_test.go

如何編寫 Adapter

所有適配器需要至少實現(xiàn)兩個適配器接口中的兩個方法:LoadPolicy(model model.Model) error and SavePolicy(model model.Model) error

其他三個功能是可選的婆咸。如果適配器支持自動保存功能竹捉,則應(yīng)該實現(xiàn)它們。

方法 類型 描述
LoadPolicy() 強制的 從存儲中加載所有策略規(guī)則
SavePolicy() 強制的 將所有策略規(guī)則保存到存儲中
AddPolicy() 可選擇的 向存儲中添加策略規(guī)則
RemovePolicy() 可選擇的 從存儲中刪除策略規(guī)則
RemoveFilteredPolicy() 可選擇的 從存儲中刪除匹配篩選器的策略規(guī)則

注意:如果適配器不支持自動保存功能尚骄,它應(yīng)該為三個可選功能提供一個空實現(xiàn)块差。如果您不提供它,編譯器將會報錯倔丈。下面是一個例子:

// GO語言示例
// AddPolicy adds a policy rule to the storage.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
    return errors.New("not implemented")
}

// RemovePolicy removes a policy rule from the storage.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
    return errors.New("not implemented")
}

// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
    return errors.New("not implemented")
}

casbin執(zhí)行器在調(diào)用這三個可選函數(shù)時將忽略未實現(xiàn)錯誤憨闰。

關(guān)于數(shù)據(jù)庫表結(jié)構(gòu)的創(chuàng)建

作為約定,適配器應(yīng)該能夠自動創(chuàng)建一個名為casbin的數(shù)據(jù)庫(如果它不存在)需五,并將其用于策略存儲鹉动。請使用xorm適配器作為參考實現(xiàn):https://github.com/casbin/xorm-adapter

Watchers

? 我們支持使用像etcd這樣的分布式消息傳遞系統(tǒng)來保持多個casbin執(zhí)行器實例之間的一致性。因此宏邮,我們的用戶可以同時使用多個Casbin enforcers來處理大量的權(quán)限檢查請求泽示。

與策略存儲 adapters類似,我們沒有把watcher的代碼放在主庫中蜜氨。 任何對新消息系統(tǒng)的支持都應(yīng)該作為atcher程序來實現(xiàn)械筛。 完整的Casbin watchers列表如下所示。 歡迎任何第三方對 watcher 進行新的貢獻飒炎,如果有請告知我們埋哟,我將把它放在這個列表中:

Watcher Type Author Description
Etcd Watcher KV store Casbin Watcher for etcd
NATS Watcher Messaging system Soluto Watcher for NATS
ZooKeeper Watcher KV store Grepsr Watcher for Apache ZooKeeper
Redis Watcher KV store @billcobbler Watcher for Redis
GCP Pub/Sub Watcher Messaging system LivingPackets Watcher for Google Cloud Platform PUB/SUB

Role Managers

角色管理器用于管理Casbin中的RBAC角色層次結(jié)構(gòu)(用戶角色映射)。 角色管理器可以從Casbin策略規(guī)則或外部源(如LDAP郎汪、Okta赤赊、Auth0闯狱、Azure AD等) 檢索角色數(shù)據(jù)。 我們支持角色管理器的不同實現(xiàn)砍鸠。 為了保持代碼輕量級扩氢,我們沒有把角色管理器代碼放在主庫中(默認的角色管理器除外)耕驰。 下面提供了Casbin角色管理器的完整列表爷辱。 歡迎任何第三方對角色管理器進行新的貢獻,如果有請告知我們朦肘,我將把它放在這個列表中:

Role manager Author Description
Default Role Manager (built-in) Casbin 支持存儲在Casbin策略中的角色層次結(jié)構(gòu)

對于開發(fā)人員:所有角色管理器都必須實現(xiàn)RoleManager接口饭弓。默認角色管理器可以用作引用實現(xiàn)。

中間件

WEB框架

Name Description
Laravel The PHP framework for web artisans, via plugin: laravel-casbin
Laravel An authorization library for the laravel framework, via plugin: Laravel Authorization
Yii PHP Framework A fast, secure, and efficient PHP framework, via plugin: yii-casbin
CakePHP Build fast, grow solid PHP Framework, via plugin: cake-casbin
CodeIgniter4 Associate users with roles and permissions in CodeIgniter4 Web Framework, via plugin: CodeIgniter Permission
ThinkPHP 5.1 The ThinkPHP 5.1 framework, via plugin: think-casbin
ThinkPHP 6.0 The ThinkPHP 6.0 framework, via plugin: think-authz
Symfony The Symfony PHP framework, via plugin: symfony-casbin

API

管理 API

提供對Casbin策略管理完全支持的基本API媒抠。

參考

全局變量 e是執(zhí)行者實例弟断。

$e = new Enforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');

獲取當前策略中顯示的主題列表:

$allSubjects = $e->getAllSubjects();

獲取當前命名策略中顯示的主題列表:

$allNamedSubjects = $e->getAllNamedSubjects("p");

獲取當前策略中顯示的對象列表:

$allObjects = $e->getAllObjects();

獲取當前命名策略中顯示的對象列表:

$allNamedObjects = $e->getAllNamedObjects("p");

獲取當前策略中顯示的操作列表:

$allActions = $e->getAllActions();

獲取當前命名策略中顯示的操作列表:

$allNamedActions = $e->getAllNamedActions("p");

獲取當前策略中顯示的角色列表

$allRoles = $e->getAllRoles();

獲取當前命名策略中顯示的角色列表:

$allNamedRoles = $e->getAllNamedRoles('g');

獲取策略中的所有授權(quán)規(guī)則:

$policy = $e->getPolicy();

獲取策略中的所有授權(quán)規(guī)則,可以指定字段篩選器:

$filteredPolicy = $e->getFilteredPolicy(0, "alice");

獲取命名策略中的所有授權(quán)規(guī)則:

$namedPolicy = $e->getNamedPolicy("p");

獲取命名策略中的所有授權(quán)規(guī)則趴生,可以指定字段過濾器:

$filteredNamedPolicy = $e->getFilteredNamedPolicy("p", 0, "bob");

獲取策略中的所有角色繼承規(guī)則:

$groupingPolicy = $e->getGroupingPolicy();

獲取策略中的所有角色繼承規(guī)則阀趴,可以指定字段篩選器:

$filteredGroupingPolicy = $e->getFilteredGroupingPolicy(0, "alice");

獲取策略中的所有角色繼承規(guī)則:

$namedGroupingPolicy = $e->getNamedGroupingPolicy("g");

獲取策略中的所有角色繼承規(guī)則

$namedGroupingPolicy = $e->getFilteredNamedGroupingPolicy("g", 0, "alice");

確定是否存在授權(quán)規(guī)則

$hasPolicy = $e->hasPolicy('data2_admin', 'data2', 'read');

?

確定是否存在命名授權(quán)規(guī)則

$hasNamedPolicy = $e->hasNamedPolicy("p", "data2_admin", "data2", "read");

向當前策略添加授權(quán)規(guī)則。 如果規(guī)則已經(jīng)存在苍匆,函數(shù)返回false刘急,并且不會添加規(guī)則。 否則浸踩,函數(shù)通過添加新規(guī)則并返回true

$added = $e->addPolicy('eve', 'data3', 'read');

向當前命名策略添加授權(quán)規(guī)則叔汁。 如果規(guī)則已經(jīng)存在,函數(shù)返回false检碗,并且不會添加規(guī)則据块。 否則,函數(shù)通過添加新規(guī)則并返回true:

$added = $e->addNamedPolicy("p", "eve", "data3", "read");

從當前策略中刪除授權(quán)規(guī)則

$removed = $e->removePolicy("alice", "data1", "read");

移除當前策略中的授權(quán)規(guī)則折剃,可以指定字段篩選器另假。 RemovePolicy 從當前策略中刪除授權(quán)規(guī)則:

$removed = $e->removeFilteredPolicy(0, "alice", "data1", "read");

從當前命名策略中刪除授權(quán)規(guī)則:

$removed = $e->removeNamedPolicy("p", "alice", "data1", "read");

從當前命名策略中移除授權(quán)規(guī)則,可以指定字段篩選器:

$removed = $e->removeFilteredNamedPolicy("p", 0, "alice", "data1", "read");

確定是否存在角色繼承規(guī)則

$has = $e->hasGroupingPolicy("alice", "data2_admin");

確定是否存在命名角色繼承規(guī)則:

$has = $e->hasNamedGroupingPolicy("g", "alice", "data2_admin");

向當前策略添加角色繼承規(guī)則怕犁。 如果規(guī)則已經(jīng)存在浪谴,函數(shù)返回false,并且不會添加規(guī)則因苹。 如果規(guī)則已經(jīng)存在苟耻,函數(shù)返回false,并且不會添加規(guī)則:

$added = $e->addGroupingPolicy("group1", "data2_admin");

將命名角色繼承規(guī)則添加到當前策略扶檐。 如果規(guī)則已經(jīng)存在凶杖,函數(shù)返回false,并且不會添加規(guī)則款筑。 否則智蝠,函數(shù)通過添加新規(guī)則并返回true:

$added = $e->addNamedGroupingPolicy("g", "group1", "data2_admin");

從當前策略中刪除角色繼承規(guī)則:

$removed = $e->removeGroupingPolicy("alice", "data2_admin");

從當前策略中移除角色繼承規(guī)則腾么,可以指定字段篩選器:

$removed = $e->removeFilteredGroupingPolicy(0, "alice");

從當前命名策略中移除角色繼承規(guī)則:

$removed = $e->removeNamedGroupingPolicy("g", "alice");

從當前命名策略中移除角色繼承規(guī)則,可以指定字段篩選器:

$removed = $e->removeFilteredNamedGroupingPolicy("g", 0, "alice");

添加自定義函數(shù):

func customFunction($key1, $key2) {
    if ($key1 == "/alice_data2/myid/using/res_id" && $key2 == "/alice_data/:resource") {
        return true;
    } elseif ($key1 == "/alice_data2/myid/using/res_id" && $key2 == "/alice_data2/:id/using/:resId") {
        return true;
    } else {
        return false;
    }
}

func customFunctionWrapper(...$args){
    $key1 := $args[0];
    $key2 := $args[1];

    return customFunction($key1, $key2);
}

$e->addFunction("keyMatchCustom", customFunctionWrapper);

RBAC API

一個更友好的RBAC API杈湾。 這個API是Management API的子集解虱。 RBAC用戶可以使用這個API來簡化代碼。

參考

全局變量 e是實施者實例漆撞。

$e = new Enforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');

獲取用戶具有的角色:

$res = $e->getRolesForUser("alice");

獲取具有角色的用戶:

$res = $e->getUsersForRole("data1_admin");

確定用戶是否具有角色:

$res = $e->hasRoleForUser("alice", "data1_admin");

為用戶添加角色殴泰。 如果用戶已經(jīng)擁有該角色(aka不受影響),則返回false:

$e->addRoleForUser("alice", "data2_admin");

刪除用戶的角色浮驳。 如果用戶沒有該角色(aka不受影響)悍汛,則返回false:

$e->deleteRoleForUser("alice", "data1_admin");

刪除用戶的所有角色。 如果用戶沒有任何角色(aka不受影響)至会,則返回false:

$e->deleteRolesForUser("alice");

刪除一個用戶离咐。 如果用戶不存在,則返回false(也就是說不受影響):

$e->deleteUser("alice");

刪除一個角色:

$e->deleteRole("data2_admin");

刪除權(quán)限奉件。 如果權(quán)限不存在宵蛀,則返回false(aka不受影響):

$e->deletePermission("read");

為用戶或角色添加權(quán)限。 如果用戶或角色已經(jīng)擁有該權(quán)限(aka不受影響)县貌,則返回false:

$e->addPermissionForUser("bob", "read");

刪除用戶或角色的權(quán)限术陶。 如果用戶或角色沒有權(quán)限(aka不受影響),則返回false:

$e->deletePermissionForUser("bob", "read");

刪除用戶或角色的權(quán)限窃这。 如果用戶或角色沒有任何權(quán)限(aka不受影響)瞳别,則返回false:

$e->deletePermissionsForUser("bob");

獲取用戶或角色的權(quán)限:

$e->getPermissionsForUser("bob");

確定用戶是否具有權(quán)限:

$e->hasPermissionForUser("alice", []string{"read"});

獲取用戶具有的隱式角色。 與GetRolesForUser() 相比杭攻,該函數(shù)除了直接角色外還檢索間接角色:

例如:

g, alice, role:admin
g, role:admin, role:user

GetRolesForUser("alice") 只能獲取到: ["role:admin"].
But GetImplicitRolesForUser("alice") 卻能獲取到: ["role:admin", "role:user"].

$e->getImplicitRolesForUser("alice");

獲取用戶或角色的隱式權(quán)限祟敛。與getPermissionsForuser()相比,此函數(shù)檢索繼承角色的權(quán)限

p, admin, data1, read
p, alice, data2, read
g, alice, admin

GetPermissionsForUser("alice") 只能獲取到: [["alice", "data2", "read"]].
But GetImplicitPermissionsForUser("alice") 卻能獲取到: [["admin", "data1", "read"], ["alice", "data2", "read"]].

$e->getImplicitPermissionsForUser("alice");

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兆解,一起剝皮案震驚了整個濱河市馆铁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锅睛,老刑警劉巖埠巨,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異现拒,居然都是意外死亡辣垒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門印蔬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勋桶,“玉大人,你說我怎么就攤上這事±裕” “怎么了捐韩?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鹃锈。 經(jīng)常有香客問我荤胁,道長,這世上最難降的妖魔是什么屎债? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任仅政,我火速辦了婚禮,結(jié)果婚禮上扔茅,老公的妹妹穿的比我還像新娘已旧。我一直安慰自己秸苗,他們只是感情好召娜,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惊楼,像睡著了一般玖瘸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檀咙,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天雅倒,我揣著相機與錄音,去河邊找鬼弧可。 笑死蔑匣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的棕诵。 我是一名探鬼主播裁良,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼校套!你這毒婦竟也來了价脾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤笛匙,失蹤者是張志新(化名)和其女友劉穎侨把,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妹孙,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡秋柄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蠢正。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺鸦。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡妆距,死狀恐怖畜眨,靈堂內(nèi)的尸體忽然破棺而出瘟判,到底是詐尸還是另有隱情,我是刑警寧澤妄壶,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響谎脯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜持寄,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一源梭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稍味,春花似錦废麻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掂碱,卻和暖如春怜姿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疼燥。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工沧卢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人醉者。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓但狭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撬即。 傳聞我的和親對象是個殘疾皇子立磁,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容