ASP.NET Core WebAPI中使用JWT Bearer认证和授权
為什么是 JWT Bearer
ASP.NET Core 在 Microsoft.AspNetCore.Authentication 下實(shí)現(xiàn)了一系列認(rèn)證, 包含?Cookie,?JwtBearer,?OAuth,?OpenIdConnect?等,
Cookie 認(rèn)證是一種比較常用本地認(rèn)證方式, 它由瀏覽器自動(dòng)保存并在發(fā)送請(qǐng)求時(shí)自動(dòng)附加到請(qǐng)求頭中, 更適用于 MVC 等純網(wǎng)頁(yè)系統(tǒng)的本地認(rèn)證.
OAuth & OpenID Connect 通常用于運(yùn)程認(rèn)證, 創(chuàng)建一個(gè)統(tǒng)一的認(rèn)證中心, 來(lái)統(tǒng)一配置和處理對(duì)于其他資源和服務(wù)的用戶認(rèn)證及授權(quán).
JwtBearer 認(rèn)證中, 客戶端通常將 JWT(一種Token) 通過 HTTP 的 Authorization header 發(fā)送給服務(wù)端, 服務(wù)端進(jìn)行驗(yàn)證. 可以方便的用于 WebAPI 框架下的本地認(rèn)證.
當(dāng)然, 也可以完全自己實(shí)現(xiàn)一個(gè)WebAPI下基于Token的本地認(rèn)證, 比如自定義Token的格式, 自己寫頒發(fā)和驗(yàn)證Token的代碼等. 這樣的話通用性并不好, 而且也需要花費(fèi)更多精力來(lái)封裝代碼以及處理細(xì)節(jié).
什么是 JWT
JWT (JSON Web Token) 是一種基于JSON的、用于在網(wǎng)絡(luò)上聲明某種主張的令牌(token)。
作為一個(gè)開放的標(biāo)準(zhǔn)(RFC 7519),定義了一種簡(jiǎn)潔的、自包含的方法,從而使通信雙方實(shí)現(xiàn)以JSON對(duì)象的形式安全的傳遞信息。
JWT通常由三部分組成: 頭信息(header), 消息體(payload)和簽名(signature)。
頭信息指定了該JWT使用的簽名算法:
消息體包含了JWT的意圖:
payload = {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}未簽名的令牌由base64url編碼的頭信息和消息體拼接而成(使用"."分隔),簽名則通過私有的key計(jì)算而成:
key = "secretkey" unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload) ?signature = HMAC-SHA256(key, unsignedToken)最后在尾部拼接上base64url編碼的簽名(同樣使用"."分隔)就是JWT了:
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)JWT常常被用作保護(hù)服務(wù)端的資源,客戶端通常將JWT通過HTTP的Authorization header發(fā)送給服務(wù)端,服務(wù)端使用自己保存的key計(jì)算、驗(yàn)證簽名以判斷該JWT是否可信。
Authorization: Bearer <token>JWT 的優(yōu)缺點(diǎn)
相比于傳統(tǒng)的 cookie-session 認(rèn)證機(jī)制,優(yōu)點(diǎn)有:
更適用分布式和水平擴(kuò)展
在cookie-session方案中,cookie內(nèi)僅包含一個(gè)session標(biāo)識(shí)符,而諸如用戶信息、授權(quán)列表等都保存在服務(wù)端的session中。如果把session中的認(rèn)證信息都保存在JWT中,在服務(wù)端就沒有session存在的必要了。當(dāng)服務(wù)端水平擴(kuò)展的時(shí)候,就不用處理session復(fù)制(session replication)/ session黏連(sticky session)或是引入外部session存儲(chǔ)了。
適用于多客戶端(特別是移動(dòng)端)的前后端解決方案
移動(dòng)端使用的往往不是網(wǎng)頁(yè)技術(shù),使用Cookie驗(yàn)證并不是一個(gè)好主意,因?yàn)槟愕煤虲ookie容器打交道,而使用Bearer驗(yàn)證則簡(jiǎn)單的多。
無(wú)狀態(tài)化
JWT 是無(wú)狀態(tài)化的,更適用于 RESTful 風(fēng)格的接口驗(yàn)證。
它的缺點(diǎn)也很明顯:
更多的空間占用
JWT 由于Payload里面包含了附件信息,占用空間往往比SESSION ID大,在HTTP傳輸中會(huì)造成性能影響。所以在設(shè)計(jì)時(shí)候需要注意不要在JWT中存儲(chǔ)太多的claim,以避免發(fā)生巨大的,過度膨脹的請(qǐng)求。
無(wú)法作廢已頒布的令牌
所有的認(rèn)證信息都在JWT中,由于在服務(wù)端沒有狀態(tài),即使你知道了某個(gè)JWT被盜取了,你也沒有辦法將其作廢。在JWT過期之前(你絕對(duì)應(yīng)該設(shè)置過期時(shí)間),你無(wú)能為力。
在 WebAPI 中使用 JWT 認(rèn)證
定義配置類 JwtIssuerOptions.cs
在 Startup.cs 里面添加相關(guān)代碼:
讀取配置:
JwtBearer驗(yàn)證:
創(chuàng)建一個(gè)控制器 AuthController.cs,用來(lái)提供簽發(fā) Token 的 API
為需要保護(hù)的API添加?[Authorize]?特性
使用 Swagger UI 或者 PostMan 等工具測(cè)試
獲取Token:
curl -X POST "http://localhost:5000/api/Auth/Login" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"userName\": \"Paul\", \"password\": \"Paul123\"}"返回值:
"{\r\n ?\"auth_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQYXVsIiwianRpIjoiM2I1YzEyMzMtZTI1YS00ZWU5LWJkNjYtY2Y0NjU2YWMzM2QzIiwiaWF0IjoxNTQ0NTg5ODY5LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUGF1bCIsImlkIjoiZDM3ZjI3Y2UtODc4MC00NDI1LTkxMzUtYjY4OGE3NmM0YzBmIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbImFkbWluaXN0cmF0b3IiLCJhcGlfYWNjZXNzIl0sIm5iZiI6MTU0NDU4OTg2OCwiZXhwIjoxNTQ0NTk3MDY4LCJpc3MiOiJTZWN1cml0eURlbW8uQXV0aGVudGljYXRpb24uSldUIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyJ9.UAWLYQ5lA6xWofWIjGsPGWtAMHEtqZSfrfVaBui2mKI\",\r\n ?\"expires_in\": 7200,\r\n ?\"token_type\": \"Bearer\"\r\n}"在?https://jwt.io/?上解析 Token 如下:
{ ?"sub": "Paul", ?"jti": "3b5c1233-e25a-4ee9-bd66-cf4656ac33d3", ?"iat": 1544589869, ?"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Paul", ?"id": "d37f27ce-8780-4425-9135-b688a76c4c0f", ?"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": ["administrator","api_access"], ?"nbf": 1544589868, ?"exp": 1544597068, ?"iss": "SecurityDemo.Authentication.JWT", ?"aud": "http://localhost:5000/"}使用 Token 訪問受保護(hù)的 API
curl -X GET "http://localhost:5000/api/Values" -H "accept: text/plain" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQYXVsIiwianRpIjoiM2I1YzEyMzMtZTI1YS00ZWU5LWJkNjYtY2Y0NjU2YWMzM2QzIiwiaWF0IjoxNTQ0NTg5ODY5LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUGF1bCIsImlkIjoiZDM3ZjI3Y2UtODc4MC00NDI1LTkxMzUtYjY4OGE3NmM0YzBmIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbImFkbWluaXN0cmF0b3IiLCJhcGlfYWNjZXNzIl0sIm5iZiI6MTU0NDU4OTg2OCwiZXhwIjoxNTQ0NTk3MDY4LCJpc3MiOiJTZWN1cml0eURlbW8uQXV0aGVudGljYXRpb24uSldUIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyJ9.UAWLYQ5lA6xWofWIjGsPGWtAMHEtqZSfrfVaBui2mKI"刷新 Token
因?yàn)镴WT在服務(wù)端是沒有狀態(tài)的, 無(wú)論用戶注銷, 修改密碼還是Token被盜取, 你都無(wú)法將其作廢. 所以給JWT設(shè)置有效期并且盡量短是很有必要的. 但我們不可能讓用戶每次Token過期后都重新輸入一次用戶名和密碼為了生成新的Token. 最好是有種方式在用戶無(wú)感知的情況下完成Token刷新. 所以這里引入了Refresh Token.
修改 JwtFactory 中的 GenerateEncodedToken 方法, 新加一個(gè)參數(shù) refreshToken, 并在包含在 response 里和 auth_token 一起返回.
修改 AuthController 中的 Login Action, 在每次客戶端請(qǐng)求 JWT Token 的時(shí)候, 同時(shí)生成一個(gè) GUID 的 refreshToken. 這個(gè) refreshToken 需要保存在數(shù)據(jù)庫(kù)或者緩存里. 這里方便演示放入了 MemoryCache 里面. 緩存的過期時(shí)間要比JWT Token的過期時(shí)間稍微長(zhǎng)一點(diǎn).
添加一個(gè)RefreshToken的接口, 接收參數(shù) refresh_token, 然后檢查 refresh_token 的有效性, 如果有效生成一個(gè)新的 auth_token 和 refresh_token 并返回. 同時(shí)需要?jiǎng)h除掉原來(lái) refresh_token 的緩存.
這里只是簡(jiǎn)單的利用緩存的過期時(shí)間和auth_token的過期時(shí)間相近從而默認(rèn) refresh_token 是有效的, 精確期間需要把對(duì)應(yīng)的 auth_token過期時(shí)間一起放入緩存, 在刷新Token的時(shí)候驗(yàn)證這個(gè)時(shí)間.
測(cè)試
獲取Token:
curl -X POST "http://localhost:5000/api/Auth/Login" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"userName\": \"Paul\", \"password\": \"Paul123\"}"返回值:
"{\r\n ?\"auth_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQYXVsIiwianRpIjoiNzA5Y2VkNjEtNWQ2ZS00N2RlLTg4NjctNzVjZGM0N2U0MWZiIiwiaWF0IjoxNTQ0NjgxOTA0LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUGF1bCIsImlkIjoiZmE3NjMxYzEtMzk0NS00MzUwLThjM2YtOWYxZDRhODU0MDFhIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbImFkbWluaXN0cmF0b3IiLCJhcGlfYWNjZXNzIl0sIm5iZiI6MTU0NDY4MTkwMywiZXhwIjoxNTQ0NjgyNTAzLCJpc3MiOiJTZWN1cml0eURlbW8uQXV0aGVudGljYXRpb24uSldUIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyJ9.tEJ-EuaI-BalW4lJEL8aeJzdryKfE440EC4cAVOW1PY\",\r\n ?\"refresh_token\": \"3093f839-fd3c-47a3-97a9-c0324e4e6b7e\",\r\n ?\"expires_in\": 600,\r\n ?\"token_type\": \"Bearer\"\r\n}"請(qǐng)求RefreshToken:
curl -X POST "http://localhost:5000/api/Auth/RefreshToken" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"userName\": \"Paul\", \"refreshToken\": \"3093f839-fd3c-47a3-97a9-c0324e4e6b7e\"}"返回新的 auth_token 和 refresh_token
"{\r\n ?\"auth_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQYXVsIiwianRpIjoiMjI2M2Y4NGEtZjlmMC00ZTM1LWI1YTUtMDdhYmI0M2UzMWQ5IiwiaWF0IjoxNTQ0NjgxOTIxLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUGF1bCIsImlkIjoiZmE3NjMxYzEtMzk0NS00MzUwLThjM2YtOWYxZDRhODU0MDFhIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbImFkbWluaXN0cmF0b3IiLCJhcGlfYWNjZXNzIl0sIm5iZiI6MTU0NDY4MTkyMSwiZXhwIjoxNTQ0NjgyNTIxLCJpc3MiOiJTZWN1cml0eURlbW8uQXV0aGVudGljYXRpb24uSldUIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyJ9.A1hXNVmkqD80GqfF69LwvarpNf5QedPvKFDcB5xA4Z0\",\r\n ?\"refresh_token\": \"b33de8ff-5213-4d37-be0b-7b561553e0f7\",\r\n ?\"expires_in\": 600,\r\n ?\"token_type\": \"Bearer\"\r\n}"使用授權(quán)
在認(rèn)證階段我們通過用戶令牌獲取到了用戶的Claims,而授權(quán)便是對(duì)這些Claims進(jìn)行驗(yàn)證, 比如是否擁有某種角色,年齡是否大于18歲(如果Claims里有年齡信息)等。
簡(jiǎn)單授權(quán)
ASP.NET Core中使用Authorize特性授權(quán), 使用AllowAnonymous特性跳過授權(quán).
基于固定角色的授權(quán)
適用于系統(tǒng)中的角色是固定的,每種角色可以訪問的Controller和Action也是固定的情景.
基于策略的授權(quán)
在ASP.NET Core中,重新設(shè)計(jì)了一種更加靈活的授權(quán)方式:基于策略的授權(quán), 它是授權(quán)的核心.
在使用基于策略的授權(quán)時(shí),首先要定義授權(quán)策略,而授權(quán)策略本質(zhì)上就是對(duì)Claims的一系列斷言。
基于角色的授權(quán)和基于Scheme的授權(quán),只是一種語(yǔ)法上的便捷,最終都會(huì)生成授權(quán)策略。
自定義策略授權(quán)
基于策略的授權(quán)中有一個(gè)很重要的概念是Requirements,每一個(gè)Requirement都代表一個(gè)授權(quán)條件。
Requirement需要繼承接口IAuthorizationRequirement。
在 ASP.NET Core 中已經(jīng)內(nèi)置了一些常用的實(shí)現(xiàn):
AssertionRequirement :使用最原始的斷言形式來(lái)聲明授權(quán)策略。
DenyAnonymousAuthorizationRequirement :用于表示禁止匿名用戶訪問的授權(quán)策略,并在AuthorizationOptions中將其設(shè)置為默認(rèn)策略。
ClaimsAuthorizationRequirement :用于表示判斷Cliams中是否包含預(yù)期的Claims的授權(quán)策略。
RolesAuthorizationRequirement :用于表示使用ClaimsPrincipal.IsInRole來(lái)判斷是否包含預(yù)期的Role的授權(quán)策略。
NameAuthorizationRequirement:用于表示使用ClaimsPrincipal.Identities.Name來(lái)判斷是否包含預(yù)期的Name的授權(quán)策略。
OperationAuthorizationRequirement:用于表示基于操作的授權(quán)策略。
除了OperationAuthorizationRequirement外,都有對(duì)應(yīng)的快捷添加方法,比如RequireClaim,RequireRole,RequireUserName等。
當(dāng)內(nèi)置的Requirement不能滿足需求時(shí),可以定義自己的Requirement. 下面基于圖中所示的用戶-角色-功能權(quán)限設(shè)計(jì)來(lái)實(shí)現(xiàn)一個(gè)自定義的驗(yàn)證策略。
添加一個(gè)靜態(tài)類 TestUsers 用于模擬用戶數(shù)據(jù)
這里只是模擬, 實(shí)際使用當(dāng)中肯定是從數(shù)據(jù)庫(kù)取數(shù)據(jù), 同時(shí)也應(yīng)該有類似于User, Role, Function, UserRole, RoleFunction等幾張表保存這些數(shù)據(jù).
創(chuàng)建類 UserService 用于獲取用戶已授權(quán)的功能列表.
創(chuàng)建 PermissionRequirement
創(chuàng)建 PermissionHandler
獲取當(dāng)前的URL, 并去當(dāng)前用戶已授權(quán)的URL List里查看. 如果匹配就驗(yàn)證成功.
在Startup.cs 的 ConfigureServices 里面注冊(cè) PermissionHandler 并添加 Policy.
添加測(cè)試代碼并測(cè)試
注意這里Controller, Action需要和用戶功能表里的URL一致
使用我們的模擬數(shù)據(jù), 用戶 Paul 兩個(gè)Action GetAdminValue 和 GetGuestValue 都可以訪問; Young 只有權(quán)限訪問 GetGuestValue; 而 Roy 只可以訪問 GetAdminValue.
基于資源的授權(quán)
有些時(shí)候, 授權(quán)需要依賴于要訪問的資源, 比如:只允許作者自己編輯和刪除所寫的博客.
這種場(chǎng)景是無(wú)法通過Authorize特性來(lái)指定授權(quán)的, 因?yàn)槭跈?quán)過濾器會(huì)在MVC的模型綁定之前執(zhí)行,無(wú)法確定所訪問的資源。此時(shí),我們需要使用基于資源的授權(quán)。
在基于資源的授權(quán)中, 我們要判斷的是用戶是否具有針對(duì)該資源的某項(xiàng)操作, 而系統(tǒng)預(yù)置的OperationAuthorizationRequirement就是用于這種場(chǎng)景中的.
在實(shí)際使用當(dāng)中, 可以通過EF Core攔截或AOP來(lái)實(shí)現(xiàn)授權(quán)驗(yàn)證與業(yè)務(wù)代碼的分離。
源代碼
github
參考
Overview of ASP.NET Core Security
AngularASPNETCore2WebApiAuth
ASP.NET Core 認(rèn)證與授權(quán)[1]:初識(shí)認(rèn)證
asp.net core策略授權(quán)
ASP.NET Core 使用 JWT 搭建分布式無(wú)狀態(tài)身份驗(yàn)證系統(tǒng)
JWT權(quán)限驗(yàn)證
Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core WebAPI中使用JWT Bearer认证和授权的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core实战项目之CMS 第十
- 下一篇: eShopOnContainers 看微