前言
哈嘍~~~ 大家周一好范舀!夏天到了,大家舒服了沒(méi)有了罪,熟話(huà)說(shuō)尿背,打敗你的不是天真,是天真熱捶惜!??
過(guò)去的一周里田藐,發(fā)生了兩件事跟大家分享下:
①、有兩個(gè)小伙伴給我提供了 Working Online 的工作吱七,簡(jiǎn)單說(shuō)了說(shuō)汽久,感覺(jué)應(yīng)該不太適合,至少我不適合踊餐,前期雙方試探的成分太多了景醇,我是不喜歡,同時(shí)也建議正在找OnLine工作的小伙伴需多多考慮可行性吝岭。
②三痰、我決定開(kāi)始《微講堂》了,具體可以參考右側(cè)公告欄窜管,因?yàn)橛行┗A(chǔ)比較薄弱的小伙伴散劫,單單提供思路還是無(wú)法入門(mén),所以提供在線(xiàn)手把手教學(xué)吧幕帆,這個(gè)我是完全無(wú)所謂获搏,看你心情吧。
這幾天通過(guò)晚上對(duì) IdentityServer4 的學(xué)習(xí)和研究失乾,發(fā)現(xiàn)這個(gè)就是一個(gè)“大坑”(不是說(shuō)功能不好常熙,是里邊有很多很多的內(nèi)容需要學(xué)習(xí)),之前看官網(wǎng)碱茁, 關(guān)于 IdentityServer4 的教程裸卫,洋洋灑灑就過(guò)去了,感覺(jué)還挺簡(jiǎn)單纽竣,發(fā)現(xiàn)要真是落地到項(xiàng)目里了墓贿,自我感覺(jué)又有了壓迫感,文末結(jié)語(yǔ)中,我簡(jiǎn)單的說(shuō)了幾點(diǎn)問(wèn)題募壕,大家可以慢慢往下走调炬,不過(guò)知識(shí)嘛,無(wú)外乎就是自己開(kāi)心學(xué)習(xí) 和 自己學(xué)習(xí)掙錢(qián)舱馅,這兩個(gè)心理缰泡,加油吧。
當(dāng)然平時(shí)工作之余代嗤,還是要照顧下前后端分離項(xiàng)目的一些東西的棘钞,基礎(chǔ)不能丟,主要是三塊地方做了修改干毅,這里簡(jiǎn)單的列一下宜猜,就不單獨(dú)的寫(xiě)文章了,希望一直在看第一個(gè)項(xiàng)目的小伙伴硝逢,有緣可以看到吧姨拥,不過(guò),就算是看不到也沒(méi)事兒渠鸽,遇到了自然就知道了:
1叫乌、Blog.Vue 首頁(yè)的閃屏處理;// 知名博主@張飛洪提出的問(wèn)題徽缚,不知道我是否修改對(duì)了憨奸;http://core-dotnet.com:8077
2、Blog.Admin 后臺(tái)框架調(diào)整優(yōu)化凿试;// ①登錄頁(yè)樣式改版排宰,②Tabs 導(dǎo)航條優(yōu)化,③兼容手機(jī)屏幕等那婉;http://core-dotnet.com:2364
3板甘、Blog.Core 后端項(xiàng)目增加 Wiki 頁(yè);// 為了讓剛接觸框架的小伙伴能快速一覽吧恃,特地在 Github 上虾啦,創(chuàng)建了 Wiki 麻诀,只不過(guò)現(xiàn)在才打了個(gè)目錄痕寓,內(nèi)容慢慢填,如果還有其他的不足之處蝇闭,歡迎提建議呻率;https://github.com/anjoy8/Blog.Core/wiki
突然轉(zhuǎn)話(huà)題,上次咱們第一次對(duì)項(xiàng)目進(jìn)行持久化操作《三║ 詳解授權(quán)持久化 & 用戶(hù)數(shù)據(jù)遷移》呻引,不知道小伙伴都看了多少礼仗,這里再把幾個(gè)重要問(wèn)題提一下,希望不要忘記了才好:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">1、Ids4 一共用到了幾個(gè)上下文元践,分別的用處是什么韭脊? 2、在遷移中单旁,數(shù)據(jù)庫(kù)生成了多少表沪羔,各個(gè)模塊又是干什么的? 3象浑、Ids4 的良好擴(kuò)展性蔫饰,體現(xiàn)在哪里?豐富性又體現(xiàn)在哪里愉豺? 4篓吁、ApplicationUser 類(lèi)是起到什么作用的?</pre>
如果腦子里有些東西蚪拦,那就恭喜了杖剪,如果第一次看,或者完全不知道我在說(shuō)什么的話(huà)驰贷,請(qǐng)看上一集摘盆,今天會(huì)說(shuō)說(shuō)我在研究遇到的兩個(gè) Flag ??,也就是兩個(gè)問(wèn)題饱苟,希望有心的小伙伴孩擂,可以幫忙思考下,歡迎找我討論箱熬,廢話(huà)不多說(shuō)类垦,開(kāi)車(chē),馬上講解今天的內(nèi)容城须!????
零蚤认、今天要實(shí)現(xiàn)綠色的部分
(知識(shí)結(jié)構(gòu)圖,注意這是我自己的講解結(jié)構(gòu)糕伐,和Ids4知識(shí)圖解無(wú)關(guān))
一砰琢、用戶(hù)數(shù)據(jù)處理 —— Identity
咱們?cè)谏掀恼轮校?jiǎn)單的將 IdentityServer4 的結(jié)構(gòu)進(jìn)行持久化處理良瞧,并把前后端項(xiàng)目中的用戶(hù)數(shù)據(jù)進(jìn)行遷移處理陪汽,最后修改了登錄頁(yè)的樣式,基本滿(mǎn)足了登錄和登出的操作褥蚯,作為一個(gè)授權(quán)服務(wù)中心挚冤,僅僅只有登錄是完全解決不了什么問(wèn)題的,至少應(yīng)該對(duì)用戶(hù)數(shù)據(jù)進(jìn)行常規(guī)操作處理赞庶,比如 CURD 等基礎(chǔ)操作训挡。
正好澳骤,我們使用了 NetCore 自帶的 Identity 機(jī)制,可以幫助我們做一部分工作澜薄,因?yàn)樗约阂卜庋b了一些方法为肮,我們可以根據(jù)他們的方法,實(shí)當(dāng)?shù)淖鲂U(kuò)展肤京,從而達(dá)到相應(yīng)的目的弥锄,具體有哪些操作,請(qǐng)往下看:
1蟆沫、用戶(hù)數(shù)據(jù)展示(有權(quán)限)
既然有數(shù)據(jù)處理籽暇,肯定得有展示出來(lái),當(dāng)然饭庞,這個(gè)不是一定的戒悠,只是做下處理,如果你擔(dān)心會(huì)有數(shù)據(jù)安全問(wèn)題的話(huà)舟山,要么不顯示數(shù)據(jù)绸狐,要么只顯示無(wú)關(guān)痛癢的兩列,甚至可以直接加上權(quán)限累盗,只有超級(jí)管理員或者技術(shù)人員可以看到就行寒矿。我這里僅僅是加了個(gè)登錄權(quán)限,只有登錄的用戶(hù)才能看的到:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">// 注入用戶(hù)管理
private readonly UserManager<ApplicationUser> _userManager;
[HttpGet]
[Route("account/users")]
[Authorize]//可以自定義規(guī)則 public IActionResult Users(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl; var users = _userManager.Users.Where(d => !d.tdIsDelete).OrderBy(d => d.UserName).ToList();//Identity 已經(jīng)對(duì)內(nèi)部的一些方法做了封裝若债,直接使用即可符相,如果你對(duì) Net 自帶的 Identity 使用過(guò)的話(huà),應(yīng)該很容易上手蠢琳。
return View(users);
}</pre>
注意下上邊的紅色標(biāo)注的地方啊终,下文會(huì)說(shuō)到為啥這里用到了 isDelete 。
我們簡(jiǎn)單的對(duì) User 頁(yè)面做了授權(quán)處理傲须,必須登錄狀態(tài)下才能有權(quán)訪(fǎng)問(wèn)蓝牲,如果是沒(méi)有登錄,會(huì)直接跳轉(zhuǎn)到登錄頁(yè)面:
(帶權(quán)限的用戶(hù)展示頁(yè))
2泰讽、注冊(cè)
關(guān)于注冊(cè)其實(shí)我們之前已經(jīng)說(shuō)過(guò)了例衍,為什么呢,因?yàn)槲覀冊(cè)谥皩?dǎo)入用戶(hù)數(shù)據(jù)的時(shí)候已卸,就已經(jīng)用到了這個(gè)方法佛玄,只不過(guò)這里單拎出來(lái)了,但是這里有一個(gè)問(wèn)題需要我們好好的思考思考咬最,那就是角色的獲若岬铡!這里就是我下邊要說(shuō)的第一個(gè)“Flag”??永乌,為什么重要呢惑申,不知道現(xiàn)在讀的你是否使用過(guò) IdentityServer4 ,我也這幾天在考慮這個(gè)問(wèn)題翅雏,授權(quán)中心肯定需要有用戶(hù)管理的圈驼,那很自然的,就會(huì)出現(xiàn) “ 區(qū)分控制 ” 的問(wèn)題望几,這里簡(jiǎn)單說(shuō)下會(huì)出現(xiàn)的兩個(gè)情況:
1绩脆、前臺(tái)展示項(xiàng)目:如果我們的vue 項(xiàng)目,是一個(gè)前臺(tái)網(wǎng)站橄抹,比如 電商類(lèi) 的或者 Blog.Vue 這樣的靴迫,很簡(jiǎn)單,我們只需要在 api 上加上 [Authorize] 這個(gè)無(wú)具體規(guī)則的授權(quán)特性就行楼誓,大家先不要往下看玉锌,先停一分鐘想一想是不是這個(gè)情況。商城嘛疟羹,只需要用戶(hù)登錄一下就可以購(gòu)買(mǎi)了主守,我們不需要特地的區(qū)分商城用戶(hù)有什么區(qū)別,有什么三六九等榄融,大家都是一樣参淫,登錄了,就可以任何操作愧杯,無(wú)論是買(mǎi)東西涎才,還是寫(xiě)文章,亦或者投票等等力九;
2憔维、后臺(tái)管理項(xiàng)目:但是!還有另一種情況畏邢,那就是后臺(tái)管理业扒,一個(gè)對(duì)用戶(hù)身份要求特別嚴(yán)格的一個(gè)系統(tǒng),我們肯定不能僅僅在 api 接口地址上舒萎,加上 [Authorize] 這個(gè)簡(jiǎn)單的特性就完事兒了程储,就比如我們的 Blog.Admin 項(xiàng)目,肯定需要一套復(fù)雜的授權(quán)策略機(jī)制臂寝,那就不得不用到用戶(hù)的角色信息章鲤,或者其他的模塊信息,這就是我上邊說(shuō)的 “區(qū)分控制”咆贬;(至于是基于角色的策略败徊,還是模塊化,我還在考慮中掏缎,目前先嘗試角色管理)
3皱蹦、猜想:你是不是想說(shuō)使用基于角色+策略授權(quán)的** Hybrid Flow 混合模式**煤杀?別著急,以后的問(wèn)題會(huì)說(shuō)到沪哺,這里提出這個(gè)問(wèn)題沈自,就是向給大家一個(gè)思路的過(guò)程徐勃。
如果是第二種情況的話(huà)疼蛾,我們?cè)谟脩?hù)注冊(cè)的時(shí)候,就需要帶上 “角色” 這個(gè)信息杀狡,比如我這里先默認(rèn)是一個(gè) test 系統(tǒng)測(cè)試管理員的角色(這個(gè)暫時(shí)這么處理籍滴,后期我會(huì)再深入研究下酪夷,是不是這個(gè)模式,或者如果正再看的你很懂的話(huà)孽惰,歡迎指導(dǎo)下晚岭,不勝感激!)灰瞻,當(dāng)然腥例,如果你的項(xiàng)目不需要對(duì)用戶(hù)的權(quán)限進(jìn)行劃分,就比如我上邊的第一種情況酝润,電商類(lèi)燎竖,博客類(lèi),只要不是后臺(tái)管理這種的前臺(tái)系統(tǒng)要销,都很簡(jiǎn)單构回,只需要在 api 上加上 [Authorize] ,然后授權(quán)中心是不需要角色這個(gè)概念的疏咐。
我們學(xué)術(shù)討論嘛纤掸,當(dāng)然是從復(fù)雜的著手,就把角色給考慮進(jìn)去了浑塞,現(xiàn)在先寫(xiě)死一個(gè)角色借跪,我們以后的文章中會(huì)進(jìn)一步討論這個(gè)復(fù)雜的情況:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;"> [HttpPost]
[Route("account/register")]
[ValidateAntiForgeryToken] public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null, string rName = "AdminTest")
{
ViewData["ReturnUrl"] = returnUrl;
IdentityResult result = new IdentityResult(); // 模型校驗(yàn)
if (ModelState.IsValid)
{ // 判斷用戶(hù)名是否存在,說(shuō)明:如果是DDD設(shè)計(jì)思想酌壕,這中查重應(yīng)該是寫(xiě)在領(lǐng)域模型的掏愁。
var userItem = _userManager.FindByNameAsync(model.LoginName).Result; if (userItem == null)
{ // 轉(zhuǎn)成我們的實(shí)體模型,說(shuō)明:這種多個(gè)實(shí)體轉(zhuǎn)換卵牍,可以使用 Dto
var user = new ApplicationUser
{
Email = model.Email,
UserName = model.LoginName,
LoginName = model.RealName,
sex = model.Sex,
age = model.Birth.Year - DateTime.Now.Year,
birth = model.Birth,
addr = "",
tdIsDelete = false }; // 創(chuàng)建用戶(hù)果港,注意密碼的規(guī)范,比如必須有大小寫(xiě)字母+數(shù)字+符號(hào)
result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded)
{ // 用戶(hù)添加成功后糊昙,就需要添加聲明了辛掠,看自己需要多少吧,可以自定義擴(kuò)展
result = await _userManager.AddClaimsAsync(user, new Claim[]{ // 這個(gè) Name 释牺,就是 Jwt 的唯一名字萝衩,也是頁(yè)面里展示的名稱(chēng)回挽,比如是“測(cè)試賬號(hào)”,而不是登錄名的“test1”
new Claim(JwtClaimTypes.Name, model.RealName), new Claim(JwtClaimTypes.Email, model.Email), // 是否需要進(jìn)行 Email 郵件驗(yàn)證
new Claim(JwtClaimTypes.EmailVerified, "false", ClaimValueTypes.Boolean), // 這里就是角色聲明
new Claim(JwtClaimTypes.Role, rName)
}); if (result.Succeeded)
{ // 添加成功欠气,可以直接登錄厅各,這個(gè)就比如是我們的博客項(xiàng)目或者電商項(xiàng)目镜撩,我們?cè)谑跈?quán)中心注冊(cè)成功后预柒,直接登錄了,跳轉(zhuǎn)到前臺(tái)了袁梗。 //await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
}
} else {
ModelState.AddModelError(string.Empty, $"{userItem?.UserName} already exists");
} // 收集全部異常數(shù)據(jù)宜鸯,返回前臺(tái)
AddErrors(result);
} return View(model);
}</pre>
上邊的就是注冊(cè)的主要代碼,大家可以自己任意的擴(kuò)展遮怜,然后重要的部分淋袖,我已經(jīng)標(biāo)紅,也寫(xiě)上了詳細(xì)的注釋?zhuān)貏e簡(jiǎn)單锯梁,都能看懂即碗。
這一 Part 都很平常,最重要的一個(gè)問(wèn)題還是那個(gè)角色這一塊陌凳,希望讀到這里的都能看懂剥懒,想一想到底你的項(xiàng)目里需不需要這樣的 Claim,不懂的歡迎來(lái)討論合敦。
3初橘、更新 與 邏輯刪除(有權(quán)限)
上邊咱們說(shuō)到了展示和添加,那下邊就是說(shuō)到更新了(這個(gè)操作我?guī)狭俗罡叩臋?quán)限充岛,必須是超級(jí)管理員才能操作 [Authorize(Roles = "SuperAdmin")] )保檐,你會(huì)問(wèn),為啥要把刪除和更新放到一起呢崔梗?其實(shí)我個(gè)人感覺(jué)邏輯是一樣的夜只,平時(shí)開(kāi)發(fā)肯定也都知道,邏輯刪除其實(shí)就是把“是否刪除” 這個(gè)字段設(shè)置成 True 就行了蒜魄,但是真的是這樣么扔亥,我們慢慢往下看。
首先更新用戶(hù)這個(gè)很簡(jiǎn)單的权悟,我就不多說(shuō)什么了砸王,具體的可以看看代碼,主要的邏輯就是平時(shí)的三步走:
1峦阁、查詢(xún)出當(dāng)前人Model谦铃;
2、用視圖模型修改Model榔昔;
3驹闰、執(zhí)行更新操作 _userManager.UpdateAsync(userItem); // 這里要說(shuō)下就是瘪菌,Identity 自帶了很多擴(kuò)展方法,大家需要自己好好的研究下嘹朗,從而達(dá)到自己的相應(yīng)目的师妙。
更新說(shuō)完了,下邊說(shuō)說(shuō)刪除屹培,刪除其實(shí)本身就有兩種情況:
1默穴、邏輯刪除,很自然褪秀,就是將數(shù)據(jù)更新下?tīng)顟B(tài)蓄诽,比如我們可以用上邊的方法,把當(dāng)前操作人的 IsDeleted=True 即可媒吗,很簡(jiǎn)單仑氛;
2、物理刪除闸英,這個(gè)還是需要好好研究研究锯岖,我在官方的代碼里,沒(méi)有找到如何物理刪除的方法甫何,可能還是需要開(kāi)發(fā)者自己定義擴(kuò)展吧出吹;
這就是我說(shuō)的第二個(gè) “Flag”?? ,需要好好的思考思考沛豌,如果你已經(jīng)忘了第一個(gè) Flag 的話(huà)趋箩,請(qǐng)向上看,用戶(hù)注冊(cè)章節(jié)里的角色問(wèn)題加派。
(更新 & 刪除 有權(quán)限 動(dòng)圖)
4叫确、重置密碼
這個(gè)是目前為止稍微復(fù)雜一點(diǎn)的,需用用到流程芍锦,首先看動(dòng)圖吧:
(重置/更新密碼 動(dòng)圖)
這個(gè)過(guò)程其實(shí)很簡(jiǎn)單竹勉,也是項(xiàng)目中必須使用到的功能,我相信任何一個(gè)網(wǎng)站娄琉,必須要用到這個(gè)重置和找回密碼的功能吧次乓,當(dāng)然生產(chǎn)環(huán)境很復(fù)雜,可能需要郵箱或者手機(jī)等來(lái)處理動(dòng)態(tài)鏈接孽水,我這里只是提供一個(gè)思路票腰,總結(jié)來(lái)說(shuō),流程說(shuō)明如下:
1女气、輸入當(dāng)時(shí)注冊(cè)郵箱杏慰;
2、獲取包含動(dòng)態(tài) Code 的安全鏈接(可通過(guò)發(fā)郵件的形式);
3缘滥、根據(jù)安全鏈接轰胁,設(shè)置新密碼;
4朝扼、重新登錄赃阀;
核心代碼(節(jié)選):
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">// 1、判斷郵箱
var user = await _userManager.FindByEmailAsync(model.Email); // 2擎颖、生成重置密碼回調(diào)鏈接
var code = await _userManager.GeneratePasswordResetTokenAsync(user); var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme); var ResetPassword = $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>"; // 3榛斯、重置密碼
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);</pre>
5、其他情況處理肠仪?
通過(guò)上邊的簡(jiǎn)單說(shuō)明肖抱,AccountController 這個(gè)控制器的內(nèi)容备典, 咱們說(shuō)完了异旧,是不是就沒(méi)有問(wèn)題了呢,不是提佣!我們要研究吮蛹,就要研究透徹,大家肯定注意到了這個(gè)項(xiàng)目中拌屏,基本都說(shuō)到了潮针,但是在核心的快速啟動(dòng)文件夾 Quickstart 中,還有幾個(gè)控制器沒(méi)有說(shuō)到:
不光如此倚喂,在平時(shí)的開(kāi)發(fā)中每篷,我們還會(huì)遇到下邊這幾個(gè)業(yè)務(wù)邏輯操作:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">1、如何找回注冊(cè)郵箱端圈? 2焦读、如何通過(guò)發(fā)送郵件,從而達(dá)到郵件確認(rèn)的目的舱权? 3矗晃、如何實(shí)現(xiàn)FaceBook、Google登錄宴倍? 4张症、如何更新用戶(hù)的角色等Claims?
5鸵贬、如何刷新 Token ?</pre>
上邊紅框中的那幾個(gè)控制器都是什么意思俗他?
下邊四條業(yè)務(wù)邏輯又該如何實(shí)現(xiàn)?
當(dāng)前項(xiàng)目是不是還有其他不為我們知道的秘密阔逼?以后的章節(jié)再慢慢展開(kāi)兆衅,請(qǐng)關(guān)注。
不過(guò)我們既然已經(jīng)完成用戶(hù)的基本操作,我們就先停下上邊的疑惑問(wèn)題涯保,往下走走诉濒,看看 IdentityServer4 到底是如何通過(guò) OpenID Connect 來(lái)操作的。
二夕春、簡(jiǎn)單授權(quán)模式 —— Implicit Flow OpenID
0未荒、OpenID Connect授權(quán)模式
OPID 認(rèn)證流程主要是由 OAuth2 的五種授權(quán)流程延伸而來(lái)的,它有以下 3 種:
- Authorization Code Flow(授權(quán)碼模式):基于OAuth2的授權(quán)碼來(lái)?yè)Q取Id Token和Access Token及志。
- Implicit Flow(簡(jiǎn)化模式):基于OAuth2的Implicit流程獲取Id Token和Access Token片排。
- Hybrid Flow:混合Authorization Code Flow+Implici Flow獲取Id Token和Access Token。
注:OpenID Connect 為什么沒(méi)有基于OAuth2的Resource Owner Password Credentials Grant和Client Credentials Grant擴(kuò)展速侈,Resource Owner Password Credentials Grant是需要應(yīng)用提供賬號(hào)密碼的率寡,賬號(hào)密碼都有了在獲取Id Token意義不大。Client Credentials Grant沒(méi)有用戶(hù)的參與所以獲取Id Token 也沒(méi)意義倚搬。這也能反映授權(quán)和認(rèn)證的差異冶共,以及只使用OAuth2來(lái)做身份認(rèn)證的事情是遠(yuǎn)遠(yuǎn)不夠的,也是不合適的每界。
1捅僵、概念
簡(jiǎn)化模式用于獲取訪(fǎng)問(wèn)令牌(但它不支持令牌的刷新,之所以所以稱(chēng)為簡(jiǎn)化模式眨层,和授權(quán)碼模式比少了獲取授權(quán)碼的步驟)庙楚,并對(duì)運(yùn)行特定重定向URI的公共客戶(hù)端進(jìn)行優(yōu)化,而這一些列操作通常會(huì)使用腳本語(yǔ)言在瀏覽器中完成趴樱,令牌對(duì)訪(fǎng)問(wèn)者是可見(jiàn)的馒闷,且客戶(hù)端也不需要驗(yàn)證。
簡(jiǎn)化模式叁征,主要有下邊三個(gè)特點(diǎn):
1纳账、用于“公共”客戶(hù)端;
2航揉、客戶(hù)端應(yīng)用直接從瀏覽器訪(fǎng)問(wèn)資源塞祈;
3、沒(méi)有顯式的客戶(hù)端身份認(rèn)證帅涂;
2议薪、結(jié)構(gòu)圖
為了配合大家理解,我這里有兩個(gè)場(chǎng)景媳友,大家腦子里先有個(gè)畫(huà)面斯议,然后往下看四個(gè)角色和流程圖:
場(chǎng)景一:博客園登錄,需要獲取騰訊的某一個(gè)QQ用戶(hù)的頭像和昵稱(chēng)等資源醇锚;
場(chǎng)景二:前后端分離哼御,Vue 項(xiàng)目需要獲取 Core 項(xiàng)目的 當(dāng)前test1賬號(hào)的 數(shù)據(jù)坯临;
首先先理解下四個(gè)角色:
1、Resource Owner(資源擁有者) —— 資源所有者恋昼,就比如我們授權(quán)登錄中的看靠,QQ用戶(hù),他才是資源的擁有者液肌。3143422472 /** test1賬號(hào)**
2挟炬、Resource Server(資源服務(wù)器) —— 資源服務(wù)器,用來(lái)存儲(chǔ)用戶(hù)資源(頭像嗦哆,昵稱(chēng)等)的服務(wù)器谤祖,比如騰訊QQ。騰訊QQ服務(wù)器 / Blog.Core
3老速、Client(客戶(hù)端) —— 第三方客戶(hù)端粥喜,比如博客園;https://www.cnblogs.com /** Blog.Vue**
4橘券、Authorization Server(授權(quán)服務(wù)器)—— 授權(quán)服務(wù)器额湘,用來(lái)作為認(rèn)證第三方平臺(tái)的服務(wù),比如騰訊的QQ互聯(lián)平臺(tái)约郁。https://graph.qq.com/oauth2.0/show?whic...... / Blog.Idp
然后咱們看看具體的流程是怎樣的:
(流程1:參考網(wǎng)上畫(huà)的缩挑,可能不是很明了)
(流程2:自己根據(jù)官網(wǎng)圖片做了下修改)
Tips:Web-Hosted Client Resource 服務(wù)器相當(dāng)于是一個(gè)存儲(chǔ) accessToken 的地方,通常指瀏覽器中的存儲(chǔ)(cookie鬓梅、localStorage、SessionStorge谨湘、js變量等)绽快,一般這個(gè)頁(yè)面是看不到的,而且一般情況是和 Client 客戶(hù)端寫(xiě)在一起的紧阔,當(dāng)然也有分開(kāi)的坊罢。
步驟解析:
客戶(hù)端攜帶客戶(hù)端標(biāo)識(shí)以及重定向URI到授權(quán)服務(wù)器;
用戶(hù)確認(rèn)是否要授權(quán)給客戶(hù)端擅耽;
授權(quán)服務(wù)器得到許可后活孩,跳轉(zhuǎn)到指定的重定向地址,并將令牌也包含在了里面乖仇;
客戶(hù)端不攜帶上次獲取到的包含令牌的片段憾儒,去請(qǐng)求資源服務(wù)器;
資源服務(wù)器會(huì)向?yàn)g覽器返回一個(gè)腳本乃沙;
瀏覽器會(huì)根據(jù)上一步返回的腳本起趾,去提取在C步驟中獲取到的令牌;
瀏覽器將令牌推送給客戶(hù)端警儒。
(A步驟)中需要用到的參數(shù)训裆,注意在這里要使用"application/x-www-form-urlencoded"格式:
response_type 必選項(xiàng),此值必須為"token"
client_id 必選項(xiàng)
redirect_uri 可選項(xiàng)
scope 可選項(xiàng)
-
state 建議選項(xiàng)
例如:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
(C步驟)中返回的參數(shù)包含:
access_token 必選項(xiàng)
token_type 必選項(xiàng)
expires_in 建議選項(xiàng)
scope 可選項(xiàng)
-
state 必選項(xiàng)
例如:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
上邊我們簡(jiǎn)單的說(shuō)了說(shuō) Implicit Flow 模式的相關(guān)知識(shí)點(diǎn),不知道大家有沒(méi)有一點(diǎn)點(diǎn)感覺(jué)边琉,如果不是很懂属百,正好感覺(jué)配合著下邊的代碼研究下,二者結(jié)合會(huì)更好变姨。
三诸老、前后端項(xiàng)目授權(quán)聯(lián)調(diào)
因?yàn)槲覀冇玫搅饲昂蠖朔蛛x項(xiàng)目,所以一定是要三方處理钳恕,如果你現(xiàn)在使用的是 MVC 模式的話(huà)别伏,我們以后的章節(jié)也會(huì)說(shuō)到 授權(quán)碼授權(quán)模式(Authorization Code Flow),這里先把簡(jiǎn)化模式調(diào)通了:
1忧额、授權(quán)服務(wù)端 —— Implicit(Blog.Idp)
這個(gè)配置很簡(jiǎn)單厘肮,在 Blog.Idp 項(xiàng)目中,大家別看是在 Config.cs 文件里睦番,其實(shí)它已經(jīng)在我們上一篇文章中类茂,生成到了數(shù)據(jù)庫(kù)中,不懂的請(qǐng)回看上一篇文章
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;"> new Client {
ClientId = "blogvuejs",//客戶(hù)端id
ClientName = "Blog.Vue JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:6688/callback" },//回調(diào)頁(yè)面
PostLogoutRedirectUris = { "http://localhost:6688" },
AllowedCorsOrigins = { "http://localhost:6688" },
// 允許的前端獲取的作用域
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile, "roles", "blog.core.api" }
}</pre>
2托嚣、資源服務(wù)端 —— Bearer(Blog.Core)
這里的配置是在 Blog.Core 我們的資源服務(wù)器中巩检,在啟動(dòng)文件 Startup.cs 中,大家自行查看示启,注意如果使用這個(gè)的話(huà)兢哭,請(qǐng)把 Jwt 認(rèn)證給注釋掉:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options => {
options.Authority = "http://localhost:5002";//授權(quán)服務(wù)器地址
options.RequireHttpsMetadata = false;//是否Https
options.ApiName = "blog.core.api";//我們?cè)?Blog.Idp 中配置的資源服務(wù)器名
});</pre>
添加過(guò)程中,可能會(huì)需要引用擴(kuò)展包 : IdentityServer4.AccessTokenValidation 這都是小問(wèn)題夫嗓,大家自行檢查即可迟螺。
3、請(qǐng)求客戶(hù)端 —— Oidc(Blog.Vue)
上邊我們已經(jīng)在兩個(gè)服務(wù)端做好了配置舍咖,客戶(hù)端如何處理矩父,這個(gè)地方才是今天的重頭戲,無(wú)論是什么客戶(hù)端排霉,JS 或者 Vue窍株、React、Ng 等等前端框架攻柠,都需要用到 oidc-client 這個(gè)插件庫(kù):
1球订、安裝
執(zhí)行命令:npm install oidc-client --save
2、封裝
注意這個(gè)是一個(gè)js庫(kù)辙诞,我們就像之前將 SignalR 那樣辙售,直接使用就行,不用在 main.js 中引用飞涂,但是還是需要先實(shí)例化一個(gè)用戶(hù)管理類(lèi) ApplicationUserManager 并配置構(gòu)造函數(shù)旦部,請(qǐng)注意這些參數(shù)都要和 Blog.Idp 授權(quán)服務(wù)器配置一致祈搜。
在 src 文件夾下 新建 Auth 文件夾,并添加 applicationusermanager.js 來(lái)封裝我們的連接管理:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">import { UserManager } from 'oidc-client'
class ApplicationUserManager extends UserManager {
constructor () {
super({
authority: 'http://localhost:5002',// 授權(quán)服務(wù)中心地址
client_id: 'blogvuejs',// 客戶(hù)端 id
redirect_uri: 'http://localhost:6688/callback',// 登錄回調(diào)地址
response_type: 'id_token token',
scope: 'openid profile roles blog.core.api',// 作用域也要一一匹配
post_logout_redirect_uri: 'http://localhost:6688' //登出后回調(diào)地址 })
} async login () { await this.signinRedirect() return this.getUser()
} async logout () { return this.signoutRedirect()
}
}</pre>
同時(shí)為了配合其他頁(yè)面使用士八,我們封裝幾個(gè)常用的方法容燕,在 Auth 文件夾下,新建 UserAuth.js 來(lái)封裝用戶(hù)的一些基本信息:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">import applicationUserManager from "./applicationusermanager"; const userAuth = {
data() { return {
user: {
name: "",
isAuthenticated: false }
};
},
methods: { async refreshUserInfo() {//獲取用戶(hù)信息 const user = await applicationUserManager.getUser(); if (user) { this.user.name = user.profile.name; this.user.isAuthenticated = true;
} else { this.user.name = ""; this.user.isAuthenticated = false;
}
}
}, async created() { await this.refreshUserInfo();
}
};
export default userAuth;</pre>
3婚度、發(fā)起 登錄/登出 請(qǐng)求
我們封裝好了方法蘸秘,下邊就是直接設(shè)計(jì)業(yè)務(wù)邏輯了,過(guò)程很簡(jiǎn)單蝗茁,在 App.vue 組件中:
1醋虏、每次路由跳轉(zhuǎn)需異步獲取用戶(hù)數(shù)據(jù);
2哮翘、發(fā)起異步登錄請(qǐng)求颈嚼;
3、發(fā)起異步登出請(qǐng)求饭寺;
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">import applicationUserManager from "./Auth/applicationusermanager";
import userAuth from "./Auth/UserAuth";
export default {
name: "app",
mixins: [userAuth],
data: function() { return {};
},
watch: {
root.store.commit("saveToken", "");
} catch (error) {
console.log(error); this.emit("show-snackbar", { message: error });
}
}
}
};</pre>
4限煞、回調(diào)
在上邊的用戶(hù)管理配置中,我們用到了一個(gè)回調(diào)頁(yè)面员凝,這個(gè)很重要署驻,因?yàn)槲覀冊(cè)诘卿洺晒螅枰{(diào)整到客戶(hù)端绊序,并且需要將信息給存儲(chǔ)下來(lái)硕舆,就是上邊流程圖中,我們說(shuō)到的 客戶(hù)端資源
具體怎么寫(xiě)的骤公,很簡(jiǎn)單,在 views 視圖頁(yè)面文件夾下扬跋,新建一個(gè) LoginCallbackView.vue 頁(yè)面:
<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">import applicationUserManager from '../Auth/applicationusermanager' export default { async created () { try { // 核心的就是這里了
await applicationUserManager.signinRedirectCallback()
let user = await applicationUserManager.getUser() // 將 token 存儲(chǔ)在客戶(hù)端
this.router.push({name: 'home'})
} catch (e) {
console.log(e) this.emit('show-snackbar', { message: e })
}
}
}</pre>
四阶捆、結(jié)語(yǔ)
本文還是延續(xù)上篇文章的快速講解的風(fēng)格,簡(jiǎn)單連貫的把用戶(hù)管理和前后端聯(lián)調(diào)的內(nèi)容通了一遍钦听,總結(jié)一下:
1洒试、分析了用戶(hù)是否需要角色等策略的緣由;
2朴上、實(shí)現(xiàn)了對(duì)用戶(hù)的基本操作——CURD+重置密碼垒棋;
3、授權(quán)項(xiàng)目中還遺留了一片未知的知識(shí)塊痪宰,亟待探索叼架;
4畔裕、實(shí)現(xiàn)了客戶(hù)端、資源服務(wù)器乖订、授權(quán)服務(wù)器的第一次聯(lián)調(diào)扮饶;
5、重點(diǎn)講解了五大模式中的 Implicit Flow 簡(jiǎn)化模式的概念和應(yīng)用場(chǎng)景乍构;
6甜无、同時(shí)也把 Hybrid Flow 混合模式給引申出來(lái),因?yàn)樗?角色+策略 的授權(quán)哥遮;
當(dāng)然岂丘,通過(guò)這一篇的學(xué)習(xí),又開(kāi)拓出了更多的未知領(lǐng)域眠饮,IdentityServer4 沒(méi)有我們想想的那么難奥帘,但是肯定也不是一個(gè) Demo 就能說(shuō)的完的簡(jiǎn)單,
如何解決文章中提到的君仆,打算提到的翩概,未提到的各種問(wèn)題呢,請(qǐng)持續(xù)關(guān)注吧返咱。