轉(zhuǎn):Keycloak授權(quán)服務(wù)指南(下)

轉(zhuǎn)自:https://www.liangzl.com/get-article-detail-124061.html

上一期:Keycloak授權(quán)服務(wù)指南(上)

授權(quán)服務(wù)端點(diǎn)發(fā)現(xiàn)和元數(shù)據(jù)

Keycloak提供了一個(gè)發(fā)現(xiàn)文檔來幫助客戶端獲取與Keycloak授權(quán)服務(wù)交互所需的任何信息栏笆,包括端點(diǎn)位置和功能。

可以從這里獲取發(fā)現(xiàn)文檔:

curl -X GET \

? http://${host}:${port}/auth/realms/${realm}/.well-known/uma2-configuration

請將上面占位符中的變量替換為實(shí)際的值。

你收到的響應(yīng)應(yīng)該是如下類型:

{

? ? // some claims are expected here

? ? // these are the main claims in the discovery document about Authorization Services endpoints location

? ? "token_endpoint": "http://${host}:${post}/auth/realms/${realm}/protocol/openid-connect/token",

? ? "token_introspection_endpoint": "http://${host}:${post}/auth/realms/${realm}/protocol/openid-connect/token/introspect",

? ? "resource_registration_endpoint": "http://${host}:${post}/auth/realms/${realm}/authz/protection/resource_set",

? ? "permission_endpoint": "http://${host}:${post}/auth/realms/${realm}/authz/protection/permission",

? ? "policy_endpoint": "http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy"

}

每個(gè)端點(diǎn)都暴露了一組功能:

token_endpoint

遵循OAuth2的Token端點(diǎn)支持 urn:ietf:params:oauth:grant-type:uma-ticket 授權(quán)類型蚌铜±ト福客戶端可以向此端點(diǎn)發(fā)送授權(quán)申請并獲取Keycloak RPT捉捅。

token_introspection_endpoint

遵循Oauth2的Token檢查端點(diǎn)厅目⊙铮客戶端可以使用該端點(diǎn)查詢RPT的狀態(tài)猴仑,并確定與token相關(guān)的任何其他信息审轮,比如Keycloak授予的權(quán)限。

resource_registration_endpoint

遵循UMA協(xié)議的資源注冊端點(diǎn)辽俗,資源服務(wù)器可以使用它來管理資源以及范圍疾渣。此端點(diǎn)提供了資源、范圍的創(chuàng)建崖飘、查詢榴捡、更新以及刪除等功能。

permission_endpoint

遵循UMA協(xié)議的權(quán)限管理端點(diǎn)朱浴,資源服務(wù)器可以用來管理權(quán)限tickets吊圾。提供了對permission ticket的創(chuàng)建、查詢翰蠢、更新以及刪除等功能项乒。

獲取權(quán)限

要從Keycloak獲取權(quán)限,需要向token端點(diǎn)發(fā)送授權(quán)請求梁沧。Keycloak會根據(jù)申請的資源以及范圍來評估關(guān)聯(lián)策略檀何,最后發(fā)送攜帶權(quán)限的RPT。

客戶端發(fā)送的授權(quán)申請可以有下列參數(shù):

grant_type

必填。必須是 urn:ietf:params:oauth:grant-type:uma-ticket埃碱。

ticket

可選猖辫。UMA授權(quán)過程中,客戶端最近一次收到的權(quán)限ticket砚殿。

claim_token

可選啃憎。Keycloak評估授權(quán)時(shí)用到的附加的聲明。它允許客戶端推送聲明到Keycloak似炎。更多細(xì)節(jié)請查看token格式中的 cliam_token_format 參數(shù)辛萍。

claim_token_format

可選。用于說明claim_token參數(shù)的格式羡藐。Keycloak支持兩種token格式:urn:ietf:params:oauth:token-type:jwt 以及https://openid.net/specs/openid-connect-core-1_0.html#IDToken贩毕。urn:ietf:params:oauth:token-type:jwt格式表示claim_token參數(shù)引用了一個(gè)access token。https://openid.net/specs/openid-connect-core-1_0.html#IDToken則表示claim_token參數(shù)引用了一個(gè)OpenID Connect ID Token仆嗦。

rpt

可選辉阶。代表一個(gè)以前發(fā)放的RPT,其權(quán)限將被評估并添加到一個(gè)新的RPT中瘩扼。此參數(shù)允許擁有RPT的客戶端執(zhí)行增量授權(quán)谆甜。

permission

可選。表示客戶端正在尋求訪問的一組資源以及范圍集绰。此參數(shù)可以重復(fù)定義规辱,以請求針對多個(gè)資源和范圍的權(quán)限。這個(gè)參數(shù)擴(kuò)展了 urn:ietf:params:oauth:grant-type: uml -ticket 授權(quán)類型栽燕,以允許客戶端在沒有權(quán)限ticket的情況下發(fā)送授權(quán)請求罕袋。字符串的格式必須是:RESOURCE_ID#SCOPE_ID。例如:Resource A#Scope A, Resource A#Scope A, Scope B, Scope C, Resource A碍岔,#Scope A浴讯。

audience

可選。正在訪問的資源服務(wù)器的客戶端標(biāo)識符付秕。如果定義了permission參數(shù)兰珍,則必須填此參數(shù)。它用作Keycloak計(jì)算權(quán)限的上下文提示询吴。

response_include_resource_name

可選掠河。指示RPT的權(quán)限中是否包含資源名稱。如果為false猛计,則只包含資源ID唠摹。

response_permissions_limit

可選。一個(gè)整型的值N奉瘤,用來表示RPT最多可攜帶的權(quán)限個(gè)數(shù)勾拉。和rpt參數(shù)一起使用煮甥,只有最近N次請求的權(quán)限會被保留在RPT中。

submit_request

可選藕赞。布爾型成肘,用來指示服務(wù)器是否要創(chuàng)建權(quán)限ticket相關(guān)資源/范圍的權(quán)限請求。此參數(shù)只有在UMA授權(quán)過程中與ticket參數(shù)一起使用時(shí)才有效斧蜕。

response_mode

可選冠息。指示服務(wù)器如何響應(yīng)授權(quán)請求垛耳『又剩可以讓服務(wù)器僅返回決策結(jié)果或者權(quán)限列表而不是完整的標(biāo)準(zhǔn)OAuth2響應(yīng)耳舅,此參數(shù)可選的值為:

-decision

指示服務(wù)器僅發(fā)送決策結(jié)果: ?

{

? ? 'result': true

}

如果授權(quán)請求沒有映射到任何資源,則返回403狀態(tài)碼均芽。

-permissions

指示服務(wù)器返回權(quán)限列表:

[

? ? {

? ? ? ? 'rsid': 'My Resource'

? ? ? ? 'scopes': ['view', 'update']

? ? },

? ? ...

]

如果授權(quán)請求沒有映射任何權(quán)限丘逸,將返回403狀態(tài)碼。

下面的例子展示了客戶端申請?jiān)L問資源服務(wù)器保護(hù)的兩個(gè)資源:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Bearer ${access_token}" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

? --data "audience={resource_server_client_id}" \

? --data "permission=Resource A#Scope A" \

? --data "permission=Resource B#Scope B"

下面的例子展示了客戶端請求訪問受保護(hù)的任意資源和范圍:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Bearer ${access_token}" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

? --data "audience={resource_server_client_id}"

下面的例子展示了客戶端在從資源服務(wù)器接收到權(quán)限ticket后掀宋,繼續(xù)請求受UMA保護(hù)的資源:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Bearer ${access_token}" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

? --data "ticket=${permission_ticket}

Keycloak會根據(jù)評估結(jié)果發(fā)放RPT:

HTTP/1.1 200 OK

Content-Type: application/json

...

{

? ? "access_token": "${rpt}",

}

RPT可以從access_token響應(yīng)參數(shù)中獲取深纲。如果客戶端未通過授權(quán)評估,Keycloak將返回403 HTTP狀態(tài)碼:

HTTP/1.1 403 Forbidden

Content-Type: application/json

...

{

? ? "error": "access_denied",

? ? "error_description": "request_denied"

}

客戶端驗(yàn)證方式

客戶端需要身份驗(yàn)證才能獲取RPT布朦。當(dāng)使用 urn:ietf:params:oauth:grant-type: uma-ticket 授權(quán)類型時(shí)囤萤,客戶端可以使用以下任何一種身份驗(yàn)證方法:

Bearer Token

此時(shí)發(fā)送給token端點(diǎn)的請求必須帶有Bearer的HTTP Authorization頭。

下面的例子展示了如何使用access token對客戶端進(jìn)行身份認(rèn)證是趴。

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Bearer ${access_token}" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"

當(dāng)客戶端代表用戶執(zhí)行操作時(shí),上邊的方法特別有用澄惊。上面的Bearer token是Keycloak已經(jīng)向(代表用戶的)客戶端發(fā)放的access token唆途。Keycloak將根據(jù)access token所代表的上下文來評估權(quán)限。例如掸驱,如果access token是向(代表用戶A的)客戶端A發(fā)放的肛搬,那么Keycloak將根據(jù)用戶A來授予權(quán)限。

Client Credentials

客戶端可以使用Keycloak提供的任意方式來認(rèn)證毕贼。如 client_id/client_secret 或者JWT温赔。

下面的例子展示了如何在請求中附帶client id以及client secret來驗(yàn)證:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Basic cGhvdGg6L7Jl13RmfWgtkk==pOnNlY3JldA==" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"

推送聲明

在從服務(wù)端獲取權(quán)限的過程中,你可以向服務(wù)端提供任意的聲明鬼癣,用來向權(quán)限評估提供信息陶贼。

如果不使用權(quán)限ticket來獲取權(quán)限,你可以發(fā)送下面的授權(quán)請求:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

? --data "claim_token=ewogICAib3JnYW5pemF0aW9uIjogWyJhY21lIl0KfQ==" \

? --data "claim_token_format=urn:ietf:params:oauth:token-type:jwt" \

? --data "client_id={resource_server_client_id}" \

? --data "client_secret={resource_server_client_secret}" \

? --data "audience={resource_server_client_id}"

claim_token參數(shù)是一個(gè)BASE64編碼的JSON待秃,編碼前的格式如下:

{

? ? "organization" : ["acme"]

}

格式中可以有一個(gè)或多個(gè)聲明拜秧,其中每個(gè)聲明的值都是一個(gè)字符串?dāng)?shù)組。?

使用UMA推送聲明

有關(guān)使用UMA和權(quán)限ticket時(shí)如何推送聲明的更多細(xì)節(jié)章郁,請查看Permission API枉氮。

User-Managed Access

Keycloak 授權(quán)服務(wù)是基于User-Managed Access的,我們簡寫為UMA。UMA協(xié)議在下面這些方面增強(qiáng)了OAuth2:

隱私

如今聊替,越來越多的數(shù)據(jù)和設(shè)備連接到云楼肪,用戶隱私也隨之成為一個(gè)巨大的問題。資源服務(wù)器可以使用UMA和Keycloak來增強(qiáng)它們的功能惹悄,以便改進(jìn)它們保護(hù)用戶隱私方面的方式淹辞,根據(jù)用戶定義的策略授予權(quán)限。

點(diǎn)對點(diǎn)授權(quán)

資源所有者(如終端用戶)可以管理對其資源的訪問俘侠,并授權(quán)其他人(如另一個(gè)終端用戶)訪問這些資源象缀。這與OAuth2不同,在OAuth2中爷速,被授予許可的是代表用戶的客戶端央星。而UMA允許資源所有者對其他用戶本身進(jìn)行異步授權(quán)。

資源共享

資源所有者可以管理其資源的權(quán)限惫东,并決定誰可以訪問特定的資源以及如何訪問莉给。Keycloak作為共享管理服務(wù),資源所有者在其上來管理自己的資源廉沮。

Keycloak符合UMA 2.0規(guī)范颓遏,提供了大部分UMA功能。

舉例來說滞时,用戶Alice(資源所有者)使用互聯(lián)網(wǎng)銀行服務(wù)(資源服務(wù)器)管理他的銀行帳戶(資源)叁幢。有一天,愛麗絲決定把她的銀行賬戶開給一個(gè)會計(jì)專業(yè)人士鮑勃(請求方)坪稽。但是曼玩,Bob應(yīng)該只能查看(范圍)Alice的帳戶。

作為資源服務(wù)器窒百,互聯(lián)網(wǎng)銀行服務(wù)必須保護(hù)Alice的銀行帳戶黍判。為此,它依賴于Keycloak資源注冊端點(diǎn)在服務(wù)器中創(chuàng)建一個(gè)代表Alice銀行賬戶的資源篙梢。

如果Bob此時(shí)試圖訪問Alice的銀行帳戶顷帖,訪問將被拒絕〔持停互聯(lián)網(wǎng)銀行服務(wù)為銀行帳戶定義了一些默認(rèn)策略贬墩。如只有所有者(Alice)才能訪問自己的銀行賬戶。

網(wǎng)上銀行服務(wù)考慮到Alice的隱私蔼水,因此允許她更改自己賬戶的策略震糖。如允許哪些人查看其銀行賬戶。為此趴腋,互聯(lián)網(wǎng)銀行服務(wù)基于Keycloak為Alice提供一個(gè)地方來選擇允許誰來訪問她的數(shù)據(jù)以及能做出哪些操作吊说。在任何時(shí)候论咏,Alice都可以對Bob撤銷或新增額外授權(quán)。

授權(quán)過程

在UMA中颁井,授權(quán)過程開始于客戶端試圖訪問受UMA保護(hù)的資源服務(wù)器時(shí)厅贪。

受UMA保護(hù)的資源服務(wù)器會要求請求帶有bearer token。下面模擬了不帶權(quán)限ticket的請求:

curl -X GET \

? http://${host}:${port}/my-resource-server/resource/1bfdfe78-a4e1-4c2d-b142-fc92b75b986f

資源服務(wù)器將一個(gè)響應(yīng)發(fā)送回客戶機(jī)雅宾,該響應(yīng)帶有一個(gè)權(quán)限ticket和一個(gè)as_uri參數(shù)养涮,該as_uri表示Keycloak服務(wù)器的地址,客戶端可以將ticket發(fā)送到此地址來獲得RPT眉抬。

下面是資源服務(wù)器的響應(yīng)贯吓,注意響應(yīng)中包含權(quán)限ticket:

HTTP/1.1 401 Unauthorized

WWW-Authenticate: UMA realm="${realm}",

? ? as_uri="https://${host}:${port}/auth/realms/${realm}",

? ? ticket="016f84e8-f9b9-11e0-bd6f-0021cc6004de"

權(quán)限ticket是Keycloak權(quán)限API發(fā)放的一種特殊token。它們代表被申請的權(quán)限(如資源以及范圍)以及其它請求中的關(guān)聯(lián)信息蜀变。只有資源服務(wù)器可以創(chuàng)建它們悄谐。

客戶機(jī)已經(jīng)擁有了權(quán)限ticket和Keycloak服務(wù)器的地址,就可以使用文檔發(fā)現(xiàn)API來獲取token endpoint的地址并發(fā)送授權(quán)請求库北。

下面的例子展示了客戶端向token endpoint請求RPT:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Bearer ${access_token}" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

? --data "ticket=${permission_ticket}

Keycloak評估通過后會發(fā)放權(quán)限相關(guān)的RPT:

HTTP/1.1 200 OK

Content-Type: application/json

...

{

? ? "access_token": "${rpt}",

}

響應(yīng)和其他授權(quán)類型的響應(yīng)類似爬舰。RPT在access_token響應(yīng)參數(shù)中,如果client沒有通過授權(quán)服務(wù)端會返回403狀態(tài)碼:

HTTP/1.1 403 Forbidden

Content-Type: application/json

...

{

? ? "error": "access_denied",

? ? "error_description": "request_denied"

}

提交權(quán)限請求

在整個(gè)授權(quán)過程中寒瓦,客戶端首先需要從受UMA保護(hù)的資源服務(wù)器獲得一個(gè)權(quán)限ticket情屹,以便在Keycloak的token端點(diǎn)用它來交換RPT。

如果Keycloak判斷客戶端無法通過授權(quán)杂腰,會返回403及request_denied錯(cuò)誤信息垃你。

HTTP/1.1 403 Forbidden

Content-Type: application/json

...

{

? ? "error": "access_denied",

? ? "error_description": "request_denied"

}

上面的響應(yīng)表示Keycloak不能對該權(quán)限ticket發(fā)放RPT。

某些時(shí)候客戶端會用到異步授權(quán)流程颈墅,并讓被資源所有者來決定是否授權(quán)蜡镶。為此,客戶端可以在對token端點(diǎn)的請求中帶上submit_request參數(shù):

curl -X POST \

? http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

? -H "Authorization: Bearer ${access_token}" \

? --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

? --data "ticket=${permission_ticket} \

? --data "submit_request=true"

當(dāng)使用submit_request參數(shù)時(shí)恤筛,Keycloak會記錄下每個(gè)被拒絕的權(quán)限申請。然后資源所有者就可以檢查并管理這些它們芹橡。

你可以將此功能視為APP中的請求訪問按鈕毒坛,用戶可以向其他用戶申請以訪問他們的資源。

管理對用戶資源的訪問

用戶可以使用Keycloak來管理對其資源的訪問林说。要啟用此功能煎殷,必須首先啟用域的用戶管理訪問,請打開Keycloak管理控制臺中的Realm設(shè)置頁面腿箩,并啟用用戶管理的訪問開關(guān)豪直。然后進(jìn)入account客戶端:

Account客戶端的左側(cè)的菜單欄會展示My Resources選項(xiàng),用戶可以在這里:

管理需要同意授權(quán)的請求

包含等待批準(zhǔn)的所有權(quán)限請求列表珠移。每個(gè)請求都關(guān)聯(lián)到請求訪問特定資源的用戶弓乙。用戶可以批準(zhǔn)或拒絕這些請求末融。

管理我的資源

包含當(dāng)前用戶的所有資源。用戶可以點(diǎn)擊資源來查看詳情或者把它們共享給其他用戶暇韧。

管理其他人共享給我的資源

包含一個(gè)共享給當(dāng)前用戶的資源列表勾习。

管理等待審批的請求

包含一個(gè)等待當(dāng)前用戶(資源所有者)審批的請求列表。

當(dāng)用戶點(diǎn)擊 “我的資源” 列表中的任何資源詳情時(shí)懈玻,將被重定向到如下頁面:

用戶可以在這里:

管理允許訪問此資源的用戶

在這里可以查看所有有權(quán)訪問此資源的用戶巧婶。可以通過點(diǎn)擊 Revoke 來收回他們的訪問權(quán)涂乌。

將資源共享給其他人

可以在這里輸入用戶名或者郵箱艺栈,來選擇為特定用戶開放權(quán)限。

保護(hù)API

保護(hù)API遵循UMA提供了一系列端點(diǎn):

資源管理

用戶可以用此端點(diǎn)來遠(yuǎn)程管理資源湾盒,并開啟策略執(zhí)行器來向服務(wù)器查詢資源的訪問權(quán)限湿右。

權(quán)限管理

UMA協(xié)議中,資源服務(wù)器可以訪問此端點(diǎn)來獲取權(quán)限ticket历涝。此端點(diǎn)還支持查詢權(quán)限以及管理權(quán)限狀態(tài)诅需。

策略API

Keycloak利用UMA保護(hù)API使資源服務(wù)器能夠管理各自用戶的權(quán)限。除了資源和權(quán)限API外荧库,Keycloak還提供了策略API堰塌。可以讓資源服務(wù)器代表其用戶設(shè)置資源的權(quán)限分衫。

使用此API要求資源服務(wù)器擁有名為protection API token(PAT)的特殊OAuth2 access token场刑。在UMA中,PAT是有著uma_protection范圍的token蚪战。

什么是PAT牵现,如何獲取它們?

一個(gè)protection API token(PAT)是有著uma_protection范圍的特殊token邀桑。創(chuàng)建資源服務(wù)器時(shí)Keycloak會自動創(chuàng)建uma_protection的角色瞎疼,并將其與客戶端的服務(wù)帳戶關(guān)聯(lián)起來。

資源服務(wù)器可以像獲取其他OAuth2 token那樣從Keycloak獲取PAT壁畸。下邊是一個(gè)curl的例子:

curl -X POST \

? ? -H "Content-Type: application/x-www-form-urlencoded" \

? ? -d 'grant_type=client_credentials&client_id=${client_id}&client_secret=${client_secret}' \

? ? "http://localhost:8080/auth/realms/${realm_name}/protocol/openid-connect/token"

上面的示例使用client_credentials授權(quán)類型從服務(wù)器獲取PAT贼急。服務(wù)器返回的響應(yīng)如下:

{

? "access_token": ${PAT},

? "expires_in": 300,

? "refresh_expires_in": 1800,

? "refresh_token": ${refresh_token},

? "token_type": "bearer",

? "id_token": ${id_token},

? "not-before-policy": 0,

? "session_state": "ccea4a55-9aec-4024-b11c-44f6f168439e"

}

Keycloak可以使用不同的方式來認(rèn)證客戶端應(yīng)用。上面使用了client_credentials方式捏萍,它要求提供一個(gè)client_id和一個(gè)client_secret太抓。你可以自由選擇其它方式來認(rèn)證。

管理資源

Keycloak提供了遵循UMA協(xié)議的端點(diǎn)來遠(yuǎn)程管理資源令杈。

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set

下面列出了此端點(diǎn)的主要功能(為了清晰起見省略了全路徑):

創(chuàng)建資源: POST /resource_set

讀取資源:GET /resource_set/{_id}

更新資源: PUT /resource_set/{_id}

刪除資源:DELETE /resource_set/{_id}

資源列表:GET /resource_set

更多信息請查看UMA資源注冊API走敌。

創(chuàng)建資源

使用下面的POST請求來創(chuàng)建資源:

curl -v -X POST \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set \

? -H 'Authorization: Bearer '$pat \

? -H 'Content-Type: application/json' \

? -d '{

? ? "name":"Tweedl Social Service",

? ? "type":"http://www.example.com/rsrcs/socialstream/140-compatible",

? ? "icon_uri":"http://www.example.com/icons/sharesocial.png",

? ? "resource_scopes":[

? ? ? ? "read-public",

? ? ? ? "post-updates",

? ? ? ? "read-private",

? ? ? ? "http://www.example.com/scopes/all"

? ? ? ]

? }'

默認(rèn)情況下,資源的所有者是資源服務(wù)器逗噩〉衾觯可以使用下面的請求來定義其他資源所有者:

curl -v -X POST \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set \

? -H 'Authorization: Bearer '$pat \

? -H 'Content-Type: application/json' \

? -d '{

? ? "name":"Alice Resource",

? ? "owner": "alice"

? }'

其中owner字段開始是用戶名或者用戶唯一標(biāo)識跌榔。

創(chuàng)建用戶管理的資源

默認(rèn)情況下,通過保護(hù)API創(chuàng)建的資源不能由資源所有者通過帳戶服務(wù)來管理机打。

要創(chuàng)建資源所有者可管理的資源矫户,你必須設(shè)置ownerManagedAccess屬性:

curl -v -X POST \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set \

? -H 'Authorization: Bearer '$pat \

? -H 'Content-Type: application/json' \

? -d '{

? ? "name":"Alice Resource",

? ? "owner": "alice",

? ? "ownerManagedAccess": true

? }'

更新資源

使用HTTP PUT請求來更新資源:

curl -v -X PUT \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set/{resource_id} \

? -H 'Authorization: Bearer '$pat \

? -H 'Content-Type: application/json' \

? -d '{

? ? "_id": "Alice Resource",

? ? "name":"Alice Resource",

? ? "resource_scopes": [

? ? ? ? "read"

? ? ]

? }'

刪除資源

使用HTTP DELETE請求來刪除資源:

curl -v -X DELETE \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set/{resource_id} \

? -H 'Authorization: Bearer '$pat

查詢資源

通過ID查詢資源:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set/{resource_id}

通過name來查詢資源:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?name=Alice Resource

通過URI來查詢資源:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?uri=/api/alice

通過owner查詢資源:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?owner=alice

通過type查詢資源:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?type=albums

通過scope查詢資源:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?scope=read

?查詢時(shí)可以通過first和max來限制結(jié)果條數(shù)。

管理權(quán)限申請

使用UMA的資源服務(wù)器可以使用端點(diǎn)來管理權(quán)限申請残邀。端點(diǎn)也提供了UMA兼容的流程來注冊權(quán)限請求以及獲取權(quán)限ticket皆辽。

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/permission

上面說到的權(quán)限ticket是一種特殊的token,代表了一個(gè)權(quán)限申請芥挣。UMA規(guī)范對權(quán)限ticket的定義如下:

它代表著一個(gè)相關(guān)句柄驱闷,從授權(quán)服務(wù)器傳遞到資源服務(wù)器、再到客戶端空免,最終回到授權(quán)服務(wù)器空另。授權(quán)服務(wù)器在授權(quán)過程中基于此句柄來選擇要評估的策略。

在大多數(shù)情況下蹋砚,你不需要直接調(diào)用此端點(diǎn)扼菠。Keycloak為資源服務(wù)器提供了策略執(zhí)行器,用來從授權(quán)服務(wù)器獲取權(quán)限ticket坝咐,再將此ticket返回客戶端應(yīng)用循榆,并且基于最終的RPT執(zhí)行授權(quán)決策。

從Keycloak獲取權(quán)限ticket的過程是由資源服務(wù)器執(zhí)行墨坚,而不是由常規(guī)客戶端執(zhí)行的秧饮。在常規(guī)的客戶端中,當(dāng)客戶端試圖訪問受保護(hù)的資源而沒有訪問該資源的必要授權(quán)時(shí)泽篮,就會獲得權(quán)限ticket盗尸。簽發(fā)權(quán)限ticket是UMA的一個(gè)重要方面,它使得資源服務(wù)器:

將資源服務(wù)器要保護(hù)的資源相關(guān)的數(shù)據(jù)從客戶端抽象出來

在Keycloak中注冊授權(quán)請求帽撑,使得資源所有者的可以管理是否同意授權(quán)

將資源服務(wù)器和授權(quán)服務(wù)器解耦泼各,使得資源服務(wù)器可以選擇不同的授權(quán)服務(wù)器來管理資源

從客戶端的角度來看,權(quán)限ticket也有著重要作用:

客戶端無需知道授權(quán)和資源是如何關(guān)聯(lián)的亏拉。權(quán)限ticket對客戶端來說是不透明的历恐。

客戶端可以訪問不同資源服務(wù)器上被不同授權(quán)服務(wù)器所保護(hù)的資源。

這些只是UMA帶來的一些好處专筷,其實(shí)UMA主要優(yōu)勢在隱私和用戶控制資源訪問方面。

創(chuàng)建權(quán)限ticket

下面的HTTP POST請求展示了如何創(chuàng)建權(quán)限Ticket:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/permission \

? -H 'Authorization: Bearer '$pat \

? -H 'Content-Type: application/json' \

? -d '[

? {

? ? "resource_id": "{resource_id}",

? ? "resource_scopes": [

? ? ? "view"

? ? ]

? }

]'

你可以在上面的請求中添加任意的聲明:

curl -X POST \

? http://${host}:${port}/auth/realms/${realm_name}/authz/protection/permission \

? -H 'Authorization: Bearer '$pat \

? -H 'Content-Type: application/json' \

? -d '[

? {

? ? "resource_id": "{resource_id}",

? ? "resource_scopes": [

? ? ? "view"

? ? ],

? ? "claims": {

? ? ? ? "organization": ["acme"]

? ? }

? }

]'

這些聲明將用于評估此權(quán)限ticket相關(guān)的策略蒸苇。?

使用策略API來管理資源權(quán)限

Keycloak利用UMA保護(hù)API來允許資源服務(wù)器管理其用戶的資源磷蛹。除了資源以及權(quán)限API之外,Keycloak還提供了策略API溪烤,來讓資源服務(wù)器代表其用戶設(shè)置資源權(quán)限味咳。

策略服務(wù)器端點(diǎn)如下:

http://${host}:${port}/auth/realms/${realm_name}/authz/protection/uma-policy/{resource_id}

訪問這些API時(shí)需要攜帶bearer token庇勃,來證明用戶允許資源服務(wù)器替代自己來管理權(quán)限。bearer token可以是從token端點(diǎn)獲取到的常規(guī)token:

資源所有者密碼驗(yàn)證授權(quán)

交換令牌槽驶,即將授予某些客戶端(公共客戶端)的access token交換為資源服務(wù)器的bearer token

將權(quán)限與資源關(guān)聯(lián)

用下面的POST請求來將權(quán)限關(guān)聯(lián)到特定資源:

curl -X POST \

? http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

? -H 'Authorization: Bearer '$access_token \

? -H 'Cache-Control: no-cache' \

? -H 'Content-Type: application/json' \

? -d '{

? ? ? ? "name": "Any people manager",

? ? ? ? "description": "Allow access to any people manager",

? ? ? ? "scopes": ["read"],

? ? ? ? "roles": ["people-manager"]

}'

上面的例子中责嚷,我們創(chuàng)建了一個(gè)權(quán)限,并將它關(guān)聯(lián)到指定 resource_id 的資源掂铐,表示有著 people-manager 角色的用戶都被授予 read 范圍罕拂。

下面展示了一個(gè)新建組訪問控制策略的例子:

curl -X POST \

? http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

? -H 'Authorization: Bearer '$access_token \

? -H 'Cache-Control: no-cache' \

? -H 'Content-Type: application/json' \

? -d '{

? ? ? ? "name": "Any people manager",

? ? ? ? "description": "Allow access to any people manager",

? ? ? ? "scopes": ["read"],

? ? ? ? "groups": ["/Managers/People Managers"]

}'

下面使用了客戶端訪問的策略:

curl -X POST \

? http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

? -H 'Authorization: Bearer '$access_token \

? -H 'Cache-Control: no-cache' \

? -H 'Content-Type: application/json' \

? -d '{

? ? ? ? "name": "Any people manager",

? ? ? ? "description": "Allow access to any people manager",

? ? ? ? "scopes": ["read"],

? ? ? ? "clients": ["my-client"]

}'

甚至可以使用JavaScript來自定義策略:

curl -X POST \

? http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

? -H 'Authorization: Bearer '$access_token \

? -H 'Cache-Control: no-cache' \

? -H 'Content-Type: application/json' \

? -d '{

? ? ? ? "name": "Any people manager",

? ? ? ? "description": "Allow access to any people manager",

? ? ? ? "scopes": ["read"],

? ? ? ? "condition": "if (isPeopleManager()) {$evaluation.grant()}"

}'

也可以組合不用的訪問控制機(jī)制。

使用HTTP PUT請求來更新權(quán)限:

curl -X PUT \

? http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{permission_id} \

? -H 'Authorization: Bearer '$access_token \

? -H 'Content-Type: application/json' \

? -d '{

? ? "id": "21eb3fed-02d7-4b5a-9102-29f3f09b6de2",

? ? "name": "Any people manager",

? ? "description": "Allow access to any people manager",

? ? "type": "uma",

? ? "scopes": [

? ? ? ? "album:view"

? ? ],

? ? "logic": "POSITIVE",

? ? "decisionStrategy": "UNANIMOUS",

? ? "owner": "7e22131a-aa57-4f5f-b1db-6e82babcd322",

? ? "roles": [

? ? ? ? "user"

? ? ]

}'

刪除權(quán)限

使用下面的DELETE請求來刪除特定的權(quán)限:

curl -X DELETE \

? http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{permission_id} \

? -H 'Authorization: Bearer '$access_token

查詢權(quán)限

查詢某資源關(guān)聯(lián)的所有權(quán)限:

http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy?resource={resource_id}

使用權(quán)限名稱查詢權(quán)限:

http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy?resource={resource_id}

查詢關(guān)聯(lián) read 范圍的權(quán)限:

http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy?scope=read

查詢?nèi)繖?quán)限:

http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy

可以使用first以及max參數(shù)來限定查詢結(jié)果全陨。

RPT

RPT是使用JSON web signature(JWS)簽發(fā)的JSON web token(JWT)爆班。RPT基于之前由Keycloak發(fā)出的OAuth2 access token來構(gòu)建,該token用于用戶自身或代表用戶的特定客戶端辱姨。

解碼RPT可以看到它的payload如下:

{

? "authorization": {

? ? ? "permissions": [

? ? ? ? {

? ? ? ? ? "resource_set_id": "d2fe9843-6462-4bfc-baba-b5787bb6e0e7",

? ? ? ? ? "resource_set_name": "Hello World Resource"

? ? ? ? }

? ? ? ]

? },

? "jti": "d6109a09-78fd-4998-bf89-95730dfd0892-1464906679405",

? "exp": 1464906971,

? "nbf": 0,

? "iat": 1464906671,

? "sub": "f1888f4d-5172-4359-be0c-af338505d86c",

? "typ": "kc_ett",

? "azp": "hello-world-authz-service"

}

從此token中柿菩,你可以從服務(wù)器獲取所有permissions聲明的權(quán)限。

注意雨涛,權(quán)限與要保護(hù)的資源/范圍直接相關(guān)枢舶,并且完全與實(shí)際授予和發(fā)出這些權(quán)限的機(jī)制解耦。

審視RPT

有些時(shí)候可能需要檢視RPT來查看有效性或者從中獲取權(quán)限替久,以便讓資源服務(wù)器來強(qiáng)制執(zhí)行授權(quán)決策凉泄。

有兩種檢視RPT的主要場景:

客戶端應(yīng)用需要檢查token有效性來獲取一個(gè)新的token

在資源服務(wù)器端執(zhí)行授權(quán)決策,特別是沒有內(nèi)置的策略執(zhí)行器能夠滿足你的要求時(shí)

獲取RPT信息

你可以從下面的端點(diǎn)來獲取RPT信息侣肄,它滿足OAuth token 檢視規(guī)范:

http://${host}:${port}/auth/realms/${realm_name}/protocol/openid-connect/token/introspect

要使用上面的端點(diǎn)來檢視一個(gè)RPT旧困,你可以發(fā)送如下請求:

curl -X POST \

? ? -H "Authorization: Basic aGVsbG8td29ybGQtYXV0aHotc2VydmljZTpzZWNyZXQ=" \

? ? -H "Content-Type: application/x-www-form-urlencoded" \

? ? -d 'token_type_hint=requesting_party_token&token=${RPT}' \

? ? "http://localhost:8080/auth/realms/hello-world-authz/protocol/openid-connect/token/introspect"

上面的請求使用了HTTP BASIC認(rèn)證,并且發(fā)送了客戶端的client ID以及secret稼锅。用于審核申請檢視RPT的客戶端身份吼具,你也可以選擇Keycloak支持的其它客戶端認(rèn)證方式。

請求中需要兩個(gè)參數(shù):

token_type_hint

此參數(shù)傳值requesting_party_token?代表你期望檢視RPT矩距。

token

使用授權(quán)過程中服務(wù)器傳回的token傳值拗盒。

服務(wù)端對上面請求的響應(yīng)如下:

{

? "permissions": [

? ? {

? ? ? "resource_id": "90ccc6fc-b296-4cd1-881e-089e1ee15957",

? ? ? "resource_name": "Hello World Resource"

? ? }

? ],

? "exp": 1465314139,

? "nbf": 0,

? "iat": 1465313839,

? "aud": "hello-world-authz-service",

? "active": true

}

如果RPT處于過期狀態(tài),響應(yīng)會直接返回:

{

? "active": false

}

每次檢視RPT時(shí)都需要調(diào)用服務(wù)端嗎锥债?

不是的陡蝇。默認(rèn)情況下RPT也是符合JWT規(guī)范的token,這意味著你完全可以自己解釋它哮肚。

如果希望在不調(diào)用遠(yuǎn)程檢視端點(diǎn)的情況下驗(yàn)證這些token登夫,可以本地解碼RPT并檢查有效性。解碼之后允趟,還可以使用token中的權(quán)限來執(zhí)行授權(quán)決策恼策。

這正是策略執(zhí)行器做的事情,請記得:

用realm的公鑰來檢查RPT簽名

根據(jù)其exp潮剪、iat和aud聲明查詢token有效性

授權(quán)客戶端的Java API

根據(jù)實(shí)際項(xiàng)目的需求涣楷,資源服務(wù)器客遠(yuǎn)程管理資源以及通過編程來檢查權(quán)限分唾。如果你正在使用Java,則可以使用授權(quán)客戶端API來訪問Keycloak授權(quán)服務(wù)狮斗。

Keycloak針對資源服務(wù)器的不同需求绽乔,提供了不同端點(diǎn)(如token端點(diǎn)、資源端點(diǎn)和權(quán)限管理端點(diǎn))碳褒。

Maven依賴

<dependencies>

? ? <dependency>

? ? ? ? <groupId>org.keycloak</groupId>

? ? ? ? <artifactId>keycloak-authz-client</artifactId>

? ? ? ? <version>${KEYCLOAK_VERSION}</version>

? ? </dependency>

</dependencies>

配置

客戶端配置在keycloak.json文件中折砸,下面是一個(gè)簡單的例子:

{

? "realm": "hello-world-authz",

? "auth-server-url" : "http://localhost:8080/auth",

? "resource" : "hello-world-authz-service",

? "credentials": {

? ? "secret": "secret"

? }

}

realm(必須)

realm的名稱。

auth-server-url(必須)

Keycloak地址骤视。也是所有Keycloak頁面以及REST符端點(diǎn)的根路徑鞍爱。通常是 https://host:port/auth 的形式。

resource(必須)

客戶端ID专酗。即客戶端在Keycloak上的唯一標(biāo)識睹逃。

客戶端身份(必須)

客戶端憑據(jù)。這是一個(gè)鍵值對祷肯,其中鍵是憑據(jù)類型沉填,值是該類型對應(yīng)的值。

一般情況下該配置文件應(yīng)該放在項(xiàng)目的classpath中佑笋,這也是客戶端尋找keycloak.json文件的默認(rèn)路徑翼闹。

創(chuàng)建授權(quán)客戶端

現(xiàn)在假定classpath中已經(jīng)有了keycloak.json,現(xiàn)在來創(chuàng)建一個(gè)AuthzClient:

? ? // create a new instance based on the configuration defined in a keycloak.json located in your classpath

? ? AuthzClient authzClient = AuthzClient.create();

獲取用戶授權(quán)

下面的例子展示了如果獲取用戶授權(quán):

// create a new instance based on the configuration defined in keycloak-authz.json

AuthzClient authzClient = AuthzClient.create();

// create an authorization request

AuthorizationRequest request = new AuthorizationRequest();

// send the entitlement request to the server in order to

// obtain an RPT with all permissions granted to the user

AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);

String rpt = response.getToken();

System.out.println("You got an RPT: " + rpt);

// now you can use the RPT to access protected resources on the resource server

下面是一個(gè)例子蒋纬,說明如何為一組資源獲取用戶權(quán)限:

// create a new instance based on the configuration defined in keycloak-authz.json

AuthzClient authzClient = AuthzClient.create();

// create an authorization request

AuthorizationRequest request = new AuthorizationRequest();

// add permissions to the request based on the resources and scopes you want to check access

request.addPermission("Default Resource");

// send the entitlement request to the server in order to

// obtain an RPT with permissions for a single resource

AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);

String rpt = response.getToken();

System.out.println("You got an RPT: " + rpt);

// now you can use the RPT to access protected resources on the resource server

使用保護(hù)API來創(chuàng)建資源

// create a new instance based on the configuration defined in keycloak-authz.json

AuthzClient authzClient = AuthzClient.create();

// create a new resource representation with the information we want

ResourceRepresentation newResource = new ResourceRepresentation();

newResource.setName("New Resource");

newResource.setType("urn:hello-world-authz:resources:example");

newResource.addScope(new ScopeRepresentation("urn:hello-world-authz:scopes:view"));

ProtectedResource resourceClient = authzClient.protection().resource();

ResourceRepresentation existingResource = resourceClient.findByName(newResource.getName());

if (existingResource != null) {

? ? resourceClient.delete(existingResource.getId());

}

// create the resource on the server

ResourceRepresentation response = resourceClient.create(newResource);

String resourceId = response.getId();

// query the resource using its newly generated id

ResourceRepresentation resource = resourceClient.findById(resourceId);

System.out.println(resource);

檢查RPT

// create a new instance based on the configuration defined in keycloak-authz.json

AuthzClient authzClient = AuthzClient.create();

// send the authorization request to the server in order to

// obtain an RPT with all permissions granted to the user

AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize();

String rpt = response.getToken();

// introspect the token

TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);

System.out.println("Token status is: " + requestingPartyToken.getActive());

System.out.println("Permissions granted by the server: ");

for (Permission granted : requestingPartyToken.getPermissions()) {

? ? System.out.println(granted);

}

策略執(zhí)行器

策略實(shí)施點(diǎn)(Policy Enforcement Point, PEP)是一種設(shè)計(jì)模式猎荠,可以由不同的方式實(shí)現(xiàn)。Keycloak提供了為不同平臺蜀备、環(huán)境和編程語言實(shí)現(xiàn)PEPs的所有必要方法关摇。Keycloak授權(quán)服務(wù)提供了一個(gè)RESTful API,并利用OAuth2授權(quán)功能使用集中式授權(quán)服務(wù)器進(jìn)行細(xì)粒度授權(quán)碾阁。

PEP負(fù)責(zé)強(qiáng)制執(zhí)行(Keycloak服務(wù)器決定的)訪問決策输虱。它在應(yīng)用程序中充當(dāng)過濾器或攔截器,以檢查是否可以根據(jù)這些決策的授權(quán)來訪問受保護(hù)的資源脂凶。

權(quán)限的執(zhí)行取決于使用的協(xié)議宪睹。當(dāng)使用UMA時(shí),策略執(zhí)行器使用RPT作為bearer token來發(fā)起請求蚕钦。這意味著客戶端在向資源服務(wù)器發(fā)送權(quán)限申請之前亭病,應(yīng)該首先從Keycloak獲取一個(gè)RPT。

但是如果沒有使用UMA嘶居,你就可以使用一個(gè)普通的access token命贴。這種情況下,策略執(zhí)行器會嘗試從服務(wù)器直接獲取權(quán)限。

如果你正在使用任何Keycloak OIDC適配器胸蛛,就可以通過向keycloak.json文件添加以下屬性來啟用策略執(zhí)行器:

{

"policy-enforcer": {}

}

當(dāng)您啟用策略強(qiáng)制器時(shí),所有發(fā)送到你的應(yīng)用的請求都會被攔截樱报,并根據(jù)Keycloak所授予的權(quán)限來檢查是否能夠訪問葬项。

策略的強(qiáng)制執(zhí)行和Keycloak創(chuàng)建的資源以及項(xiàng)目項(xiàng)目路徑強(qiáng)關(guān)聯(lián)。默認(rèn)情況下迹蛤,創(chuàng)建資源服務(wù)器時(shí)民珍,Keycloak會為資源服務(wù)器創(chuàng)建一個(gè)默認(rèn)配置,這樣就可以快速地啟用策略強(qiáng)制執(zhí)行盗飒。

配置

上面提高過嚷量,在keycloak.json文件中添加policy-enforcer來啟動策略執(zhí)行:

{

? "policy-enforcer": {}

}

如果要手動定義資源,可以寫的復(fù)雜一點(diǎn):

{

? "policy-enforcer": {

? ? "user-managed-access" : {},

? ? "enforcement-mode" : "ENFORCING"

? ? "paths": [

? ? ? {

? ? ? ? "path" : "/someUri/*",

? ? ? ? "methods" : [

? ? ? ? ? {

? ? ? ? ? ? "method": "GET",

? ? ? ? ? ? "scopes" : ["urn:app.com:scopes:view"]

? ? ? ? ? },

? ? ? ? ? {

? ? ? ? ? ? "method": "POST",

? ? ? ? ? ? "scopes" : ["urn:app.com:scopes:create"]

? ? ? ? ? }

? ? ? ? ]

? ? ? },

? ? ? {

? ? ? ? "name" : "Some Resource",

? ? ? ? "path" : "/usingPattern/{id}",

? ? ? ? "methods" : [

? ? ? ? ? {

? ? ? ? ? ? "method": "DELETE",

? ? ? ? ? ? "scopes" : ["urn:app.com:scopes:delete"]

? ? ? ? ? }

? ? ? ? ]

? ? ? },

? ? ? {

? ? ? ? "path" : "/exactMatch"

? ? ? },

? ? ? {

? ? ? ? "name" : "Admin Resources",

? ? ? ? "path" : "/usingWildCards/*"

? ? ? }

? ? ]

? }

}

下面來解釋這個(gè)配置文件:

policy-enforcer

定義如何實(shí)際執(zhí)行策略逆趣,以及希望保護(hù)的路徑蝶溶。如果沒有指定,策略強(qiáng)制器將查詢服務(wù)器宣渗,以查找與受保護(hù)的資源服務(wù)器相關(guān)聯(lián)的所有資源抖所。在這種情況下,需要確保資源的URIS被正確配置痕囱。

-user-managed-access

指定適配器使用UMA協(xié)議田轧。如果指定了,適配器將向服務(wù)器查詢權(quán)限ticket鞍恢,并根據(jù)UMA規(guī)范將它們返回給客戶端傻粘。如果沒有指定,則策略強(qiáng)制器將基于常規(guī)access token或RPT來執(zhí)行權(quán)限帮掉。在這種情況下弦悉,拒絕訪問資源前策略執(zhí)行器將嘗試直接從服務(wù)器獲得權(quán)限。

-enforcement-mode

指定策略如何強(qiáng)制執(zhí)行旭寿。

ENFORCING

????????? ? 這是默認(rèn)模式警绩,當(dāng)沒有策略關(guān)聯(lián)到指定資源時(shí),請求會被拒絕盅称。

PERMISSIVE

? ? ? ? ? ? 當(dāng)沒有策略關(guān)聯(lián)到指定資源時(shí)肩祥,請求會被允許。

DISABLE

完全禁用策略評估缩膝,允許訪問任何資源混狠。禁用后,應(yīng)用程序仍然能夠通過授權(quán)上下文獲得Keycloak授予的所有權(quán)限疾层。

-on-deny-reirect-to

定義一個(gè)URL将饺,用于拒絕訪問時(shí)重定向客戶端請求。默認(rèn)情況下,適配器使用403 HTTP狀態(tài)碼進(jìn)行響應(yīng)予弧。

-path-cache

定義策略執(zhí)行器應(yīng)如何跟蹤應(yīng)用中的路徑及Keycloak的資源之的關(guān)聯(lián)刮吧。通過這些配置關(guān)聯(lián),可以避免到Keycloak的不必要請求掖蛤。

lifespan

? ? ? ? ? ? 本地緩存的過期時(shí)間杀捻,默認(rèn)為3000,設(shè)置為0或者以下的值會禁用緩存蚓庭。

max-entries

? ? ? ? ? ? 定義本地緩存的條數(shù)致讥,如果沒有提供時(shí)默認(rèn)為1000條。

-paths

指定要保護(hù)的路徑器赞。此配置是可選的垢袱。如果沒有定義,策略執(zhí)行器將查詢Keycloak來獲取本客戶端對應(yīng)的全部路徑港柜,并使用Keycloak上配置的URIS當(dāng)做本地路徑请契。

name

給定路徑關(guān)聯(lián)的服務(wù)器上資源的名稱。當(dāng)與path一起使用時(shí)潘懊,策略強(qiáng)制器將忽略資源的URIS屬性姚糊,而使用配置提供的路徑。

path

必填授舟。相對路徑的URI救恨。如果指定了此選項(xiàng),策略執(zhí)行器將向服務(wù)器查詢相同值的URI的資源释树。目前支持基本的路徑匹配邏輯肠槽。有效路徑的例子如下:

通配符: /*

后綴:/*.html

子路徑:/path/*

路徑參數(shù): /resource/{id}

精確匹配: /resource

數(shù)組:?/{version}/resource, /api/{version}/resource, /api/{version}/resource/*

methods

? ? ? ? ? ? 用于保護(hù)的HTTP方法(例如GET、POST奢啥、PATCH)秸仙,以及它們?nèi)绾闻c服務(wù)器中給定資源的范圍相關(guān)聯(lián)。

method:HTTP方法的名稱

scopes:關(guān)聯(lián)的范圍數(shù)組桩盲。當(dāng)范圍與特定method關(guān)聯(lián)時(shí)寂纪,試圖訪問受保護(hù)資源的客戶端必須提供一個(gè)RPT用來檢查范圍權(quán)限。例如定義了create?范圍來表示POST赌结,則RPT必須包含對此路徑的create?范圍的訪問權(quán)限捞蛋。

scopes-enforcement-mode:范圍的執(zhí)行模式。值可以是ALLANY柬姚。如果是ALL拟杉,則必須授權(quán)給所有已定義的范圍。如果是ANY量承,至少應(yīng)該授權(quán)給一個(gè)范圍搬设。默認(rèn)情況下穴店,強(qiáng)制模式設(shè)置為ALL扑馁。

enforcement-mode

????????? ?指定策略如何執(zhí)行贱案。

ENFORCING:默認(rèn)模式挠铲,沒有policy關(guān)聯(lián)的資源拒絕方案剃斧。

DIABLED:禁用。

claim-information-point

????????????定義一組個(gè)聲明妄田,這些聲明必須被解析并推送到Keycloak服務(wù)器拄氯,以便策略使用啄育。有關(guān)詳細(xì)信息该窗,請參見這里

-lazy-load-paths

指定適配器應(yīng)如何獲取與應(yīng)用路徑關(guān)聯(lián)的資源的服務(wù)器蚤霞。如果為true酗失,則策略執(zhí)行器將根據(jù)請求的路徑按需獲取資源。當(dāng)不想在部署期間從服務(wù)器獲取所有資源(沒有提供路徑時(shí))昧绣,或者只定義了一個(gè)子路徑集规肴,并且希望按需獲取其它路徑時(shí),這種配置特別有用夜畴。

-http-method-as-scope

指定范圍應(yīng)如何映射到HTTP方法拖刃。如果設(shè)置為true,策略執(zhí)行器將使用當(dāng)前請求中的HTTP方法來檢查是否應(yīng)該授予訪問權(quán)限贪绘。啟用時(shí)兑牡,請確保Keycloak配置的資源范圍與要保護(hù)的路徑HTTP方法正確對應(yīng)。

-claim-information-point

定義一組全局聲明税灌,這些聲明必須被解析并推送到Keycloak服務(wù)器均函,以便策略評估使用。有關(guān)詳細(xì)信息菱涤,請參見這里苞也。

聲明信息點(diǎn)

聲明信息點(diǎn)(Claim Informatica Point, 簡寫為CIP)負(fù)責(zé)處理聲明并推到Keycloak服務(wù)器粘秆,以便提供關(guān)于策略上下文的更多信息如迟。可以將它們定義為策略執(zhí)行器的配置選項(xiàng)攻走,以便處理來自不同來源的聲明殷勘,例如:

HTTP請求(參數(shù)、header陋气、body 等)

外部HTTP服務(wù)

配置中的靜態(tài)值

實(shí)現(xiàn)了Claim Information Provider SPI的任意資源

當(dāng)將聲明推送到Keycloak服務(wù)器時(shí)劳吠,策略不僅可以基于當(dāng)前用戶,還可以基于給定事務(wù)的人巩趁、事務(wù)痒玩、時(shí)間淳附、地點(diǎn)等等內(nèi)容來作為運(yùn)行上下文考慮并做出決策。即如何使用運(yùn)行時(shí)信息來支持細(xì)粒度的授權(quán)決策蠢古。

從HTTP請求中獲取信息

下面的keycloak.json配置說明了如何從HTTP請求中來解析信息:

"policy-enforcer": {

? ? "paths": [

? ? ? {

? ? ? ? "path": "/protected/resource",

? ? ? ? "claim-information-point": {

? ? ? ? ? "claims": {

? ? ? ? ? ? "claim-from-request-parameter": "{request.parameter['a']}",

? ? ? ? ? ? "claim-from-header": "{request.header['b']}",

? ? ? ? ? ? "claim-from-cookie": "{request.cookie['c']}",

? ? ? ? ? ? "claim-from-remoteAddr": "{request.remoteAddr}",

? ? ? ? ? ? "claim-from-method": "{request.method}",

? ? ? ? ? ? "claim-from-uri": "{request.uri}",

? ? ? ? ? ? "claim-from-relativePath": "{request.relativePath}",

? ? ? ? ? ? "claim-from-secure": "{request.secure}",

? ? ? ? ? ? "claim-from-json-body-object": "{request.body['/a/b/c']}",

? ? ? ? ? ? "claim-from-json-body-array": "{request.body['/d/1']}",

? ? ? ? ? ? "claim-from-body": "{request.body}",

? ? ? ? ? ? "claim-from-static-value": "static value",

? ? ? ? ? ? "claim-from-multiple-static-value": ["static", "value"],

? ? ? ? ? ? "param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']} "

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? ]

? }

從外部HTTP服務(wù)獲取信息

"policy-enforcer": {

? ? "paths": [

? ? ? {

? ? ? ? "path": "/protected/resource",

? ? ? ? "claim-information-point": {

? ? ? ? ? "http": {

? ? ? ? ? ? "claims": {

? ? ? ? ? ? ? "claim-a": "/a",

? ? ? ? ? ? ? "claim-d": "/d",

? ? ? ? ? ? ? "claim-d0": "/d/0",

? ? ? ? ? ? ? "claim-d-all": ["/d/0", "/d/1"]

? ? ? ? ? ? },

? ? ? ? ? ? "url": "http://mycompany/claim-provider",

? ? ? ? ? ? "method": "POST",

? ? ? ? ? ? "headers": {

? ? ? ? ? ? ? "Content-Type": "application/x-www-form-urlencoded",

? ? ? ? ? ? ? "header-b": ["header-b-value1", "header-b-value2"],

? ? ? ? ? ? ? "Authorization": "Bearer {keycloak.access_token}"

? ? ? ? ? ? },

? ? ? ? ? ? "parameters": {

? ? ? ? ? ? ? "param-a": ["param-a-value1", "param-a-value2"],

? ? ? ? ? ? ? "param-subject": "{keycloak.access_token['/sub']}",

? ? ? ? ? ? ? "param-user-name": "{keycloak.access_token['/preferred_username']}",

? ? ? ? ? ? ? "param-other-claims": "{keycloak.access_token['/custom_claim']}"

? ? ? ? ? ? }

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? ]

? }

靜態(tài)聲明

"policy-enforcer": {

? ? "paths": [

? ? ? {

? ? ? ? "path": "/protected/resource",

? ? ? ? "claim-information-point": {

? ? ? ? ? "claims": {

? ? ? ? ? ? "claim-from-static-value": "static value",

? ? ? ? ? ? "claim-from-multiple-static-value": ["static", "value"],

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? ]

? }

Cliam Informatica Provider 的SPI

當(dāng)內(nèi)置CIP不能滿足實(shí)際需求時(shí)奴曙,開發(fā)人員可以使用SPI來支持不同的CIP。

例如草讶,要實(shí)現(xiàn)一個(gè)新的CIP提供者洽糟,需要實(shí)現(xiàn)?org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory 以及ClaimInformationPointProvide,并且在項(xiàng)目的classpath中提供 META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory 文件堕战。

下面是一個(gè)例子:

public class MyClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<MyClaimInformationPointProvider> {

? ? @Override

? ? public String getName() {

? ? ? ? return "my-claims";

? ? }

? ? @Override

? ? public void init(PolicyEnforcer policyEnforcer) {

? ? }

? ? @Override

? ? public MyClaimInformationPointProvider create(Map<String, Object> config) {

? ? ? ? return new MyClaimInformationPointProvider(config);

? ? }

}

每個(gè)CIP提供程序都要提供一個(gè)名稱坤溃,如上面在MyClaimInformationPointProviderFactory.getName方法。該名稱必須與policy- enforcer配置的claim-information-point一致嘱丢,Keycloak通過此名稱定位到實(shí)現(xiàn)薪介。

當(dāng)處理請求時(shí),策略執(zhí)行器將調(diào)用MyClaimInformationPointProviderFactory.create 方法越驻,來獲取MyClaimInformationPointProvider實(shí)例汁政。調(diào)用時(shí),此CIP provider定義的任何配置(通過CIP)都將傳給Keycloak缀旁。

下面是一個(gè)Provider的例子:

public class MyClaimInformationPointProvider implements ClaimInformationPointProvider {

? ? private final Map<String, Object> config;

? ? public ClaimsInformationPointProvider(Map<String, Object> config) {

? ? ? ? this.config = config;

? ? }

? ? @Override

? ? public Map<String, List<String>> resolve(HttpFacade httpFacade) {

? ? ? ? Map<String, List<String>> claims = new HashMap<>();

? ? ? ? // put whatever claim you want into the map

? ? ? ? return claims;

? ? }

}

獲取授權(quán)上下文

當(dāng)啟用策略強(qiáng)制執(zhí)行時(shí)记劈,可以通過org.keycloak.AuthorizationContext從服務(wù)器來獲得授權(quán)信息。這個(gè)類提供了幾個(gè)方法并巍,可以使用它們來獲取權(quán)限并判斷是否為特定的資源或范圍授予了權(quán)限目木。

下面的代碼展示如何從Servlet容器中獲取授權(quán)上下文:

? ? HttpServletRequest request = ... // obtain javax.servlet.http.HttpServletRequest

? ? KeycloakSecurityContext keycloakSecurityContext =

? ? ? ? (KeycloakSecurityContext) request

? ? ? ? ? ? .getAttribute(KeycloakSecurityContext.class.getName());

? ? AuthorizationContext authzContext =

? ? ? ? keycloakSecurityContext.getAuthorizationContext();

獲取KeycloakSecurityContext的信息視具體的適配器而定,請查看運(yùn)行平臺的適配器配置履澳。上面的例子用于運(yùn)行在servlet容器中的應(yīng)用嘶窄。

授權(quán)上下文可以幫助你根據(jù)Keycloak決策來構(gòu)建更多功能。例如距贷,可以使用它構(gòu)建一個(gè)僅展示已授權(quán)資源的動態(tài)菜單:

if (authzContext.hasResourcePermission("Project Resource")) {

? ? // user can access the Project Resource

}

if (authzContext.hasResourcePermission("Admin Resource")) {

? ? // user can access administration resources

}

if (authzContext.hasScopePermission("urn:project.com:project:create")) {

? ? // user can create new projects

}

AuthorizationContext代表了Keycloak授權(quán)服務(wù)的主要功能之一柄冲。從上面的示例可以看出受保護(hù)的資源與管理它們的策略沒有直接關(guān)聯(lián)。

在基于角色的訪問控制(RBAC)權(quán)限控制中它們被寫作這樣:

if (User.hasRole('user')) {

? ? // user can access the Project Resource

}

if (User.hasRole('admin')) {

? ? // user can access administration resources

}

if (User.hasRole('project-manager')) {

? ? // user can create new projects

}

雖然這兩個(gè)示例都處理相同的需求忠蝗,著手點(diǎn)不同现横。在RBAC中,角色僅隱式地定義對其資源的訪問阁最。使用Keycloak戒祠,可以創(chuàng)建更易于管理的代碼,這些代碼直接關(guān)注資源本身速种,無論使用的是RBAC姜盈、基于屬性的訪問控制(ABAC)還是任何其他BAC。

假設(shè)我們項(xiàng)目的安全性需求發(fā)生了變化配阵,除了項(xiàng)目經(jīng)理之外馏颂,PMOs現(xiàn)在也可以新建項(xiàng)目示血。

如果使用了Keycloak,你完全不需要更改應(yīng)用程序就可以處理新的需求救拉。一旦應(yīng)用程序基于資源和范圍來構(gòu)建难审,就可以只更改授權(quán)服務(wù)器中特定資源關(guān)聯(lián)的權(quán)限或策略。在這種情況下亿絮,只需要更改與項(xiàng)目資源和/或范圍 urn:project.com:project:create 關(guān)聯(lián)的權(quán)限和策略告喊。

使用AuthorizationContext獲取授權(quán)客戶端實(shí)例

AuthorizationContext 還可用于獲取你項(xiàng)目中配置的授權(quán)客戶端API的引用:

? ? ClientAuthorizationContext clientContext = ClientAuthorizationContext.class.cast(authzContext);

? ? AuthzClient authzClient = clientContext.getClient();

某些情況下,受策略執(zhí)行器保護(hù)的資源服務(wù)器需要訪問授權(quán)服務(wù)器提供的API派昧。有了AuthzClient?實(shí)例黔姜,資源服務(wù)器就可以與授權(quán)服務(wù)器交互,編程式地創(chuàng)建資源或檢查特定的權(quán)限蒂萎。

JavaScript集成

Keycloak服務(wù)器附帶一個(gè)JavaScript庫地淀,可以使用它與受策略執(zhí)行器保護(hù)的資源服務(wù)器進(jìn)行交互。這個(gè)庫基于Keycloak JavaScript適配器岖是,你的客戶端可以直接集成它來從Keycloak服務(wù)器獲得權(quán)限。

可以從Keycloak服務(wù)實(shí)例上在線獲取它:

<script src="http://.../auth/js/keycloak-authz.js"></script>

然后可以方便地創(chuàng)建KeycloakAuthorization實(shí)例:

var keycloak = ... // obtain a Keycloak instance from keycloak.js library

var authorization = new KeycloakAuthorization(keycloak);

keycloak-authz.js 庫主要提供了兩個(gè)功能:

如果資源服務(wù)器開啟了UMA实苞,則可以使用權(quán)限ticket來從Keycloak獲得權(quán)限豺撑。

發(fā)送請求的資源和范圍,申請從服務(wù)器獲得權(quán)限黔牵。

在這兩種情況下聪轿,js庫都允許你方便的和資源服務(wù)器或者Keycloak進(jìn)行交互以獲取token,然后客戶端可以將其作為bearer token來訪問資源服務(wù)器上受保護(hù)的資源猾浦。

處理來自UMA保護(hù)的資源服務(wù)器的授權(quán)響應(yīng)

如果資源服務(wù)器由策略執(zhí)行器保護(hù)陆错,就將根據(jù)bearer token上攜帶的權(quán)限來做出響應(yīng)。當(dāng)攜帶的bearer token無權(quán)訪問請求的資源時(shí)金赦,服務(wù)器將響應(yīng)401狀態(tài)碼及WWW-Authenticate頭音瓷。

HTTP/1.1 401 Unauthorized

WWW-Authenticate: UMA realm="${realm}",

? ? as_uri="https://${host}:${post}/auth/realms/${realm}",

? ? ticket="016f84e8-f9b9-11e0-bd6f-0021cc6004de"

UMA授權(quán)流程查看更多細(xì)節(jié)。

客戶端可以從資源服務(wù)器返回的WWW-Authenticate頭中提取權(quán)ticket夹抗,并使js庫來發(fā)送如下授權(quán)請求如下:

// prepare a authorization request with the permission ticket

var authorizationRequest = {};

authorizationRequest.ticket = ticket;

// send the authorization request, if successful retry the request

Identity.authorization.authorize(authorizationRequest).then(function (rpt) {

? ? // onGrant

}, function () {

? ? // onDeny

}, function () {

? ? // onError

});

authorize函數(shù)是完全異步的绳慎,使用回調(diào)來接收服務(wù)器響應(yīng):

onGrant:函數(shù)的第一個(gè)參數(shù)。如果授權(quán)成功漠烧,回調(diào)函數(shù)將接收到服務(wù)器返回的RPT杏愤。

onDeny:函數(shù)的第二個(gè)參數(shù)。僅當(dāng)服務(wù)器拒絕授權(quán)請求時(shí)調(diào)用已脓。

onError:第三個(gè)參數(shù)珊楼。僅當(dāng)出現(xiàn)異常時(shí)調(diào)用。

大多數(shù)應(yīng)用程序應(yīng)該使用onGrant回調(diào)函數(shù)在401響應(yīng)之后重試請求度液。后續(xù)請求應(yīng)該用此RPT作為重試的bearer token厕宗。

獲取權(quán)限

keycloak-authz.js 庫提供了一個(gè) entitlement 函數(shù)画舌,用于從服務(wù)器獲得RPT。

下面的例子展示了如何獲取用戶可以訪問的全部資源和范圍:

authorization.entitlement('my-resource-server-id').then(function (rpt) {

? ? // onGrant callback function.

? ? // If authorization was successful you'll receive an RPT

? ? // with the necessary permissions to access the resource server

});

下面的例子展示了如何獲得具有特定資源和范圍權(quán)限的RPT:

authorization.entitlement('my-resource-server', {

? ? "permissions": [

? ? ? ? {

? ? ? ? ? ? "id" : "Some Resource"

? ? ? ? }

? ? ]

}).then(function (rpt) {

? ? // onGrant

});

在使用 entitlement 函數(shù)時(shí)媳瞪,必須提供要訪問的資源服務(wù)器的client_id骗炉。

授權(quán)函數(shù)是完全異步的,支持一些回調(diào)函數(shù)來接收來自服務(wù)器的通知:

onGrant:函數(shù)的第一個(gè)參數(shù)蛇受。如果授權(quán)成功句葵,回調(diào)函數(shù)將接收到服務(wù)器返回的RPT。

onDeny:函數(shù)的第二個(gè)參數(shù)兢仰。僅當(dāng)服務(wù)器拒絕授權(quán)請求時(shí)調(diào)用乍丈。

onError:第三個(gè)參數(shù)。僅當(dāng)出現(xiàn)異常時(shí)調(diào)用把将。

授權(quán)請求

authorize 和 authorize 函數(shù)都接受一個(gè)授權(quán)請求對象轻专。該對象可以通過以下屬性設(shè)置:

permissions

表示資源和范圍的對象數(shù)組。例如:

var authorizationRequest = {

? "permissions": [

? ? ? {

? ? ? ? ? "id" : "Some Resource",

? ? ? ? ? "scopes" : ["view", "edit"]

? ? ? }

? ]

}

metadata

定義服務(wù)器應(yīng)如何處理授權(quán)請求察蹲。

-response_include_resource_name

布爾值请垛,指示服務(wù)器資源名是否應(yīng)該包含在RPT的權(quán)限中。如果為false洽议,則只包含資源標(biāo)識符宗收。

-response_permissions_limit

整數(shù)N,它定義了RPT可以攜帶的權(quán)限數(shù)量上限亚兄。當(dāng)與rpt參數(shù)一起使用時(shí)混稽,只有最后N個(gè)請求的權(quán)限將保留在rpt中。

submit_request

布爾值审胚,指示服務(wù)器是否需要創(chuàng)建對權(quán)限ticket的權(quán)限請求匈勋。此參數(shù)只有在與ticket參數(shù)一起,且在UMA授權(quán)流程時(shí)才會生效膳叨。

獲取RPT

如果已經(jīng)使用庫提供的任何授權(quán)函數(shù)獲得了RPT洽洁,則始終可以從授權(quán)對象獲得RPT,如下所示:

var rpt = authorization.rpt;

設(shè)置TLS/HTTPS

如果server使用的是HTTPS懒鉴,請?jiān)赼dapter中加入以下配置诡挂,來讓授權(quán)客戶端使用TLS/HTTPS協(xié)議訪問Keycloak:

{

? "truststore": "path_to_your_trust_store",

? "truststore-password": "trust_store_password"

}

強(qiáng)烈建議通過HTTPS訪問Keycloak。

源文檔更新于2019-03-13临谱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末璃俗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子悉默,更是在濱河造成了極大的恐慌城豁,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抄课,死亡現(xiàn)場離奇詭異唱星,居然都是意外死亡雳旅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門间聊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來攒盈,“玉大人,你說我怎么就攤上這事哎榴⌒突恚” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵尚蝌,是天一觀的道長迎变。 經(jīng)常有香客問我,道長飘言,這世上最難降的妖魔是什么衣形? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮姿鸿,結(jié)果婚禮上谆吴,老公的妹妹穿的比我還像新娘。我一直安慰自己苛预,他們只是感情好纪铺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碟渺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪突诬。 梳的紋絲不亂的頭發(fā)上苫拍,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音旺隙,去河邊找鬼绒极。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蔬捷,可吹牛的內(nèi)容都是我干的垄提。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼周拐,長吁一口氣:“原來是場噩夢啊……” “哼铡俐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妥粟,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤审丘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勾给,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滩报,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锅知,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脓钾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片售睹。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖可训,靈堂內(nèi)的尸體忽然破棺而出昌妹,到底是詐尸還是另有隱情,我是刑警寧澤沉噩,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布捺宗,位于F島的核電站,受9級特大地震影響川蒙,放射性物質(zhì)發(fā)生泄漏蚜厉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一畜眨、第九天 我趴在偏房一處隱蔽的房頂上張望昼牛。 院中可真熱鬧,春花似錦康聂、人聲如沸贰健。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伶椿。三九已至,卻和暖如春氓侧,著一層夾襖步出監(jiān)牢的瞬間脊另,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工约巷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留偎痛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓独郎,卻偏偏與公主長得像踩麦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子氓癌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354