《進擊吧!Blazor!》是本人與張善友老師合作的Blazor零基礎入門系列視頻,此系列能讓一個從未接觸過Blazor的程式員掌握開發Blazor應用的能力, 視頻地址:https://space.bilibili.com/483888821/channel/detail?cid=151273 本系列文章是基于《進擊吧!Blazor!》直播內容撰寫,升級.Net5,改進問題,講解更全面,因為篇幅有限,文章中省略了部分代碼,完整示例代碼:https://github.com/TimChen44/Blazor-ToDo
作者:陳超超 Ant Design Blazor 專案貢獻者,擁有十多年從業經驗,長期基于.Net技術堆疊進行架構與開發產品的作業,現就職于正泰集團, 郵箱:timchen@live.com 歡迎各位讀者有任何問題聯系我,我們共同進步,
我的的ToDo應用基本功能已經完成,但是自己的待辦當然只有自己知道,所以我們這次給我們的應用增加一些安全方面的功能,
Blazor 身份驗證與授權
身份驗證
Blazor Server應用和 Blazor WebAssembly 應用的安全方案有所不同,
Blazor WebAssembly 應用在客戶端上運行, 由于用戶可繞過客戶端檢查,因為用戶可修改所有客戶端代碼, 因此授權僅用于確定要顯示的 UI 選項,所有客戶端應用程式技術都是如此,
Blazor Server應用通過使用 SignalR 創建的實時連接運行, 建立連接后,將處理基于 SignalR 的應用的身份驗證, 可基于 cookie 或一些其他持有者令牌進行身份驗證,
授權
AuthorizeView 組件根據用戶是否獲得授權來選擇性地顯示 UI 內容, 如果只需要為用戶顯示資料,而不需要在程序邏輯中使用用戶的標識,那么此方法很有用,
< AuthorizeView>
< Authorized>
<!--驗證通過顯示-->
</ Authorized>
< NotAuthorized>
<!--驗證不通過顯示-->
</ NotAuthorized>
</ AuthorizeView>
Blazor 中使用Token
在Blazor WebAssembly模式下, 因為應用都在客戶端運行,所以使用Token作為身份認證的方式是一個比較好的選擇, 基本的使用時序圖如下
<style>#mermaid-svg-7lIxBlMiPqCvXtE6 .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .label text{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .node rect,#mermaid-svg-7lIxBlMiPqCvXtE6 .node circle,#mermaid-svg-7lIxBlMiPqCvXtE6 .node ellipse,#mermaid-svg-7lIxBlMiPqCvXtE6 .node polygon,#mermaid-svg-7lIxBlMiPqCvXtE6 .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-7lIxBlMiPqCvXtE6 .node .label{text-align:center;fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .node.clickable{cursor:pointer}#mermaid-svg-7lIxBlMiPqCvXtE6 .arrowheadPath{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-7lIxBlMiPqCvXtE6 .flowchart-link{stroke:#333;fill:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-7lIxBlMiPqCvXtE6 .edgeLabel rect{opacity:0.9}#mermaid-svg-7lIxBlMiPqCvXtE6 .edgeLabel span{color:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-7lIxBlMiPqCvXtE6 .cluster text{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-7lIxBlMiPqCvXtE6 .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-7lIxBlMiPqCvXtE6 text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .actor-line{stroke:grey}#mermaid-svg-7lIxBlMiPqCvXtE6 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .sequenceNumber{fill:#fff}#mermaid-svg-7lIxBlMiPqCvXtE6 #sequencenumber{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 #crosshead path{fill:#333;stroke:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .messageText{fill:#333;stroke:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-7lIxBlMiPqCvXtE6 .labelText,#mermaid-svg-7lIxBlMiPqCvXtE6 .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .loopText,#mermaid-svg-7lIxBlMiPqCvXtE6 .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-7lIxBlMiPqCvXtE6 .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-7lIxBlMiPqCvXtE6 .noteText,#mermaid-svg-7lIxBlMiPqCvXtE6 .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-7lIxBlMiPqCvXtE6 .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-7lIxBlMiPqCvXtE6 .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-7lIxBlMiPqCvXtE6 .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .section{stroke:none;opacity:0.2}#mermaid-svg-7lIxBlMiPqCvXtE6 .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-7lIxBlMiPqCvXtE6 .section2{fill:#fff400}#mermaid-svg-7lIxBlMiPqCvXtE6 .section1,#mermaid-svg-7lIxBlMiPqCvXtE6 .section3{fill:#fff;opacity:0.2}#mermaid-svg-7lIxBlMiPqCvXtE6 .sectionTitle0{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .sectionTitle1{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .sectionTitle2{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .sectionTitle3{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-7lIxBlMiPqCvXtE6 .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .grid path{stroke-width:0}#mermaid-svg-7lIxBlMiPqCvXtE6 .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-7lIxBlMiPqCvXtE6 .task{stroke-width:2}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText:not([font-size]){font-size:11px}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-7lIxBlMiPqCvXtE6 .task.clickable{cursor:pointer}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText0,#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText1,#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText2,#mermaid-svg-7lIxBlMiPqCvXtE6 .taskText3{fill:#fff}#mermaid-svg-7lIxBlMiPqCvXtE6 .task0,#mermaid-svg-7lIxBlMiPqCvXtE6 .task1,#mermaid-svg-7lIxBlMiPqCvXtE6 .task2,#mermaid-svg-7lIxBlMiPqCvXtE6 .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutside0,#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutside2{fill:#000}#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutside1,#mermaid-svg-7lIxBlMiPqCvXtE6 .taskTextOutside3{fill:#000}#mermaid-svg-7lIxBlMiPqCvXtE6 .active0,#mermaid-svg-7lIxBlMiPqCvXtE6 .active1,#mermaid-svg-7lIxBlMiPqCvXtE6 .active2,#mermaid-svg-7lIxBlMiPqCvXtE6 .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-7lIxBlMiPqCvXtE6 .activeText0,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeText1,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeText2,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeText3{fill:#000 !important}#mermaid-svg-7lIxBlMiPqCvXtE6 .done0,#mermaid-svg-7lIxBlMiPqCvXtE6 .done1,#mermaid-svg-7lIxBlMiPqCvXtE6 .done2,#mermaid-svg-7lIxBlMiPqCvXtE6 .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-7lIxBlMiPqCvXtE6 .doneText0,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneText1,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneText2,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneText3{fill:#000 !important}#mermaid-svg-7lIxBlMiPqCvXtE6 .crit0,#mermaid-svg-7lIxBlMiPqCvXtE6 .crit1,#mermaid-svg-7lIxBlMiPqCvXtE6 .crit2,#mermaid-svg-7lIxBlMiPqCvXtE6 .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCrit0,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCrit1,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCrit2,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCrit0,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCrit1,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCrit2,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-7lIxBlMiPqCvXtE6 .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-7lIxBlMiPqCvXtE6 .milestoneText{font-style:italic}#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCritText0,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCritText1,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCritText2,#mermaid-svg-7lIxBlMiPqCvXtE6 .doneCritText3{fill:#000 !important}#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCritText0,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCritText1,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCritText2,#mermaid-svg-7lIxBlMiPqCvXtE6 .activeCritText3{fill:#000 !important}#mermaid-svg-7lIxBlMiPqCvXtE6 .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-7lIxBlMiPqCvXtE6 g.classGroup text .title{font-weight:bolder}#mermaid-svg-7lIxBlMiPqCvXtE6 g.clickable{cursor:pointer}#mermaid-svg-7lIxBlMiPqCvXtE6 g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-7lIxBlMiPqCvXtE6 g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-7lIxBlMiPqCvXtE6 .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-7lIxBlMiPqCvXtE6 .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .dashed-line{stroke-dasharray:3}#mermaid-svg-7lIxBlMiPqCvXtE6 #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 .commit-id,#mermaid-svg-7lIxBlMiPqCvXtE6 .commit-msg,#mermaid-svg-7lIxBlMiPqCvXtE6 .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-7lIxBlMiPqCvXtE6 g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-7lIxBlMiPqCvXtE6 g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-7lIxBlMiPqCvXtE6 g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-7lIxBlMiPqCvXtE6 .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-7lIxBlMiPqCvXtE6 .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-7lIxBlMiPqCvXtE6 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-7lIxBlMiPqCvXtE6 .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-7lIxBlMiPqCvXtE6 .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-7lIxBlMiPqCvXtE6 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-7lIxBlMiPqCvXtE6 .edgeLabel text{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-7lIxBlMiPqCvXtE6 .node circle.state-start{fill:black;stroke:black}#mermaid-svg-7lIxBlMiPqCvXtE6 .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-7lIxBlMiPqCvXtE6 #statediagram-barbEnd{fill:#9370db}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-state .divider{stroke:#9370db}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-7lIxBlMiPqCvXtE6 .note-edge{stroke-dasharray:5}#mermaid-svg-7lIxBlMiPqCvXtE6 .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-7lIxBlMiPqCvXtE6 .error-icon{fill:#522}#mermaid-svg-7lIxBlMiPqCvXtE6 .error-text{fill:#522;stroke:#522}#mermaid-svg-7lIxBlMiPqCvXtE6 .edge-thickness-normal{stroke-width:2px}#mermaid-svg-7lIxBlMiPqCvXtE6 .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-7lIxBlMiPqCvXtE6 .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-7lIxBlMiPqCvXtE6 .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-7lIxBlMiPqCvXtE6 .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-7lIxBlMiPqCvXtE6 .marker{fill:#333}#mermaid-svg-7lIxBlMiPqCvXtE6 .marker.cross{stroke:#333}
:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style>
<style>#mermaid-svg-7lIxBlMiPqCvXtE6 {
color: rgba(0, 0, 0, 0.75);
font: ;
}</style>
前端
服務端
登錄請求
驗證身份
創建Token
回傳Token
業務請求
包含Token
驗證Token
成功
前端
服務端
對于安全要求不高的應用采用這個方法簡單、易維護,完全沒有問題,
但是Token本身在安全性上存在以下兩個風險:
Token無法注銷,所以可以在Token有效期內發送的非法請求,服務端無能為力, Token通過AES加密存盤在客戶端,理論上可以進行離線破解,破解后就能任意偽造Token,
因此遇到安全要求非常高的應用時,我們需要認證服務進行Token的有效性驗證
<style>#mermaid-svg-nJ5XJRPtTFEhcXNx .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .label text{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .node rect,#mermaid-svg-nJ5XJRPtTFEhcXNx .node circle,#mermaid-svg-nJ5XJRPtTFEhcXNx .node ellipse,#mermaid-svg-nJ5XJRPtTFEhcXNx .node polygon,#mermaid-svg-nJ5XJRPtTFEhcXNx .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-nJ5XJRPtTFEhcXNx .node .label{text-align:center;fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .node.clickable{cursor:pointer}#mermaid-svg-nJ5XJRPtTFEhcXNx .arrowheadPath{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-nJ5XJRPtTFEhcXNx .flowchart-link{stroke:#333;fill:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-nJ5XJRPtTFEhcXNx .edgeLabel rect{opacity:0.9}#mermaid-svg-nJ5XJRPtTFEhcXNx .edgeLabel span{color:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-nJ5XJRPtTFEhcXNx .cluster text{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-nJ5XJRPtTFEhcXNx .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-nJ5XJRPtTFEhcXNx text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .actor-line{stroke:grey}#mermaid-svg-nJ5XJRPtTFEhcXNx .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .sequenceNumber{fill:#fff}#mermaid-svg-nJ5XJRPtTFEhcXNx #sequencenumber{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx #crosshead path{fill:#333;stroke:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .messageText{fill:#333;stroke:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-nJ5XJRPtTFEhcXNx .labelText,#mermaid-svg-nJ5XJRPtTFEhcXNx .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .loopText,#mermaid-svg-nJ5XJRPtTFEhcXNx .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-nJ5XJRPtTFEhcXNx .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-nJ5XJRPtTFEhcXNx .noteText,#mermaid-svg-nJ5XJRPtTFEhcXNx .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-nJ5XJRPtTFEhcXNx .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-nJ5XJRPtTFEhcXNx .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-nJ5XJRPtTFEhcXNx .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .section{stroke:none;opacity:0.2}#mermaid-svg-nJ5XJRPtTFEhcXNx .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-nJ5XJRPtTFEhcXNx .section2{fill:#fff400}#mermaid-svg-nJ5XJRPtTFEhcXNx .section1,#mermaid-svg-nJ5XJRPtTFEhcXNx .section3{fill:#fff;opacity:0.2}#mermaid-svg-nJ5XJRPtTFEhcXNx .sectionTitle0{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .sectionTitle1{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .sectionTitle2{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .sectionTitle3{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-nJ5XJRPtTFEhcXNx .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .grid path{stroke-width:0}#mermaid-svg-nJ5XJRPtTFEhcXNx .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-nJ5XJRPtTFEhcXNx .task{stroke-width:2}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText:not([font-size]){font-size:11px}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-nJ5XJRPtTFEhcXNx .task.clickable{cursor:pointer}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText0,#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText1,#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText2,#mermaid-svg-nJ5XJRPtTFEhcXNx .taskText3{fill:#fff}#mermaid-svg-nJ5XJRPtTFEhcXNx .task0,#mermaid-svg-nJ5XJRPtTFEhcXNx .task1,#mermaid-svg-nJ5XJRPtTFEhcXNx .task2,#mermaid-svg-nJ5XJRPtTFEhcXNx .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutside0,#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutside2{fill:#000}#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutside1,#mermaid-svg-nJ5XJRPtTFEhcXNx .taskTextOutside3{fill:#000}#mermaid-svg-nJ5XJRPtTFEhcXNx .active0,#mermaid-svg-nJ5XJRPtTFEhcXNx .active1,#mermaid-svg-nJ5XJRPtTFEhcXNx .active2,#mermaid-svg-nJ5XJRPtTFEhcXNx .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-nJ5XJRPtTFEhcXNx .activeText0,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeText1,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeText2,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeText3{fill:#000 !important}#mermaid-svg-nJ5XJRPtTFEhcXNx .done0,#mermaid-svg-nJ5XJRPtTFEhcXNx .done1,#mermaid-svg-nJ5XJRPtTFEhcXNx .done2,#mermaid-svg-nJ5XJRPtTFEhcXNx .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-nJ5XJRPtTFEhcXNx .doneText0,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneText1,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneText2,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneText3{fill:#000 !important}#mermaid-svg-nJ5XJRPtTFEhcXNx .crit0,#mermaid-svg-nJ5XJRPtTFEhcXNx .crit1,#mermaid-svg-nJ5XJRPtTFEhcXNx .crit2,#mermaid-svg-nJ5XJRPtTFEhcXNx .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCrit0,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCrit1,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCrit2,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCrit0,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCrit1,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCrit2,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-nJ5XJRPtTFEhcXNx .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-nJ5XJRPtTFEhcXNx .milestoneText{font-style:italic}#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCritText0,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCritText1,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCritText2,#mermaid-svg-nJ5XJRPtTFEhcXNx .doneCritText3{fill:#000 !important}#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCritText0,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCritText1,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCritText2,#mermaid-svg-nJ5XJRPtTFEhcXNx .activeCritText3{fill:#000 !important}#mermaid-svg-nJ5XJRPtTFEhcXNx .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-nJ5XJRPtTFEhcXNx g.classGroup text .title{font-weight:bolder}#mermaid-svg-nJ5XJRPtTFEhcXNx g.clickable{cursor:pointer}#mermaid-svg-nJ5XJRPtTFEhcXNx g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-nJ5XJRPtTFEhcXNx g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-nJ5XJRPtTFEhcXNx .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-nJ5XJRPtTFEhcXNx .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .dashed-line{stroke-dasharray:3}#mermaid-svg-nJ5XJRPtTFEhcXNx #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx .commit-id,#mermaid-svg-nJ5XJRPtTFEhcXNx .commit-msg,#mermaid-svg-nJ5XJRPtTFEhcXNx .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-nJ5XJRPtTFEhcXNx g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-nJ5XJRPtTFEhcXNx g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-nJ5XJRPtTFEhcXNx g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-nJ5XJRPtTFEhcXNx .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-nJ5XJRPtTFEhcXNx .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-nJ5XJRPtTFEhcXNx .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-nJ5XJRPtTFEhcXNx .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-nJ5XJRPtTFEhcXNx .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-nJ5XJRPtTFEhcXNx .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-nJ5XJRPtTFEhcXNx .edgeLabel text{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nJ5XJRPtTFEhcXNx .node circle.state-start{fill:black;stroke:black}#mermaid-svg-nJ5XJRPtTFEhcXNx .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-nJ5XJRPtTFEhcXNx #statediagram-barbEnd{fill:#9370db}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-state .divider{stroke:#9370db}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-nJ5XJRPtTFEhcXNx .note-edge{stroke-dasharray:5}#mermaid-svg-nJ5XJRPtTFEhcXNx .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-nJ5XJRPtTFEhcXNx .error-icon{fill:#522}#mermaid-svg-nJ5XJRPtTFEhcXNx .error-text{fill:#522;stroke:#522}#mermaid-svg-nJ5XJRPtTFEhcXNx .edge-thickness-normal{stroke-width:2px}#mermaid-svg-nJ5XJRPtTFEhcXNx .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-nJ5XJRPtTFEhcXNx .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-nJ5XJRPtTFEhcXNx .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-nJ5XJRPtTFEhcXNx .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-nJ5XJRPtTFEhcXNx .marker{fill:#333}#mermaid-svg-nJ5XJRPtTFEhcXNx .marker.cross{stroke:#333}
:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style>
<style>#mermaid-svg-nJ5XJRPtTFEhcXNx {
color: rgba(0, 0, 0, 0.75);
font: ;
}</style>
前端
認證服務
服務端
登錄請求
驗證身份
創建Token
回傳Token
業務請求
包含Token
請求驗證Token
驗證Token
Token有效
成功
前端
認證服務
服務端
改造ToDo
接著我們對之前的ToDo專案進行改造,讓他支持登錄功能,
ToDo.Shared
先把前后端互動所需的Dto創建了
public class LoginDto
{
public string UserName { get ; set ; }
public string Password { get ; set ; }
}
public class UserDto
{
public string Name { get ; set ; }
public string Token { get ; set ; }
}
ToDo.Server
先改造服務端,添加必要參考,撰寫身份認證代碼等
添加參考
Microsoft.AspNetCore.Authentication.JwtBearer
Startup.cs
添加JwtBearer配置
public void ConfigureServices ( IServiceCollection services)
{
//......
services. AddAuthentication ( JwtBearerDefaults. AuthenticationScheme)
. AddJwtBearer ( options = >
{
options. TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true , //是否驗證Issuer
ValidateAudience = true , //是否驗證Audience
ValidateLifetime = true , //是否驗證失效時間
ValidateIssuerSigningKey = true , //是否驗證SecurityKey
ValidAudience = "guetClient" , //Audience
ValidIssuer = "guetServer" , //Issuer,這兩項和簽發jwt的設定一致
IssuerSigningKey = new SymmetricSecurityKey ( Encoding. UTF8. GetBytes ( "123456789012345678901234567890123456789" ) ) //拿到SecurityKey
} ;
} ) ;
}
此處定義了Token的密鑰,規則等,實際專案時可以將這些資訊放到配置中,
AuthController.cs
行政驗證控制器,用于驗證用戶身份,創建Token等,
[ ApiController ]
[ Route ( "api/[controller]/[action]" ) ]
public class AuthController : ControllerBase
{
//登錄
[ HttpPost ]
public UserDto Login ( LoginDto dto)
{
//模擬獲得Token
var jwtToken = GetToken ( dto. UserName) ;
return new ( ) { Name = dto. UserName, Token = jwtToken } ;
}
//獲得用戶,當頁面客戶端頁面重繪時呼叫以獲得用戶資訊
[ HttpGet ]
public UserDto GetUser ( )
{
if ( User. Identity. IsAuthenticated) //如果Token有效
{
var name = User. Claims. First ( x = > x. Type == ClaimTypes. Name) . Value; //從Token中拿出用戶ID
//模擬獲得Token
var jwtToken = GetToken ( name) ;
return new UserDto ( ) { Name = name, Token = jwtToken } ;
}
else
{
return new UserDto ( ) { Name = null , Token = null } ;
}
}
public string GetToken ( string name)
{
//此處加入賬號密碼驗證代碼
var claims = new Claim [ ]
{
new Claim ( ClaimTypes. Name, name) ,
new Claim ( ClaimTypes. Role, "Admin" ) ,
} ;
var key = new SymmetricSecurityKey ( System. Text. Encoding. UTF8. GetBytes ( "123456789012345678901234567890123456789" ) ) ;
var expires = DateTime. Now. AddDays ( 30 ) ;
var token = new JwtSecurityToken (
issuer: "guetServer" ,
audience: "guetClient" ,
claims: claims,
notBefore: DateTime. Now,
expires: expires,
signingCredentials: new SigningCredentials ( key, SecurityAlgorithms. HmacSha256) ) ;
return new JwtSecurityTokenHandler ( ) . WriteToken ( token) ;
}
}
ToDo.Client
改造客戶端,讓客戶端支持身份認證
添加參考
Microsoft.AspNetCore.Components.Authorization
AuthenticationStateProvider
AuthenticationStateProvider 是 AuthorizeView 組件和 CascadingAuthenticationState 組件用于獲取身份驗證狀態的基礎服務, 通常不直接使用 AuthenticationStateProvider,直接使用主要缺點是,如果基礎身份驗證狀態資料發生更改,不會自動通知組件,其次是專案中總會有一些自定義的認證邏輯, 所以我們通常寫一個類繼承他,并重寫一些我們自己的邏輯,
//AuthProvider.cs
public class AuthProvider : AuthenticationStateProvider
{
private readonly HttpClient HttpClient;
public string UserName { get ; set ; }
public AuthProvider ( HttpClient httpClient)
{
HttpClient = httpClient;
}
public async override Task< AuthenticationState> GetAuthenticationStateAsync ( )
{
//這里獲得用戶登錄狀態
var result = await HttpClient. GetFromJsonAsync < UserDto > ( $"api/Auth/GetUser" ) ;
if ( result? . Name == null )
{
MarkUserAsLoggedOut ( ) ;
return new AuthenticationState ( new ClaimsPrincipal ( ) ) ;
}
else
{
var claims = new List < Claim > ( ) ;
claims. Add ( new Claim ( ClaimTypes. Name, result. Name) ) ;
var authenticatedUser = new ClaimsPrincipal ( new ClaimsIdentity ( claims, "apiauth" ) ) ;
return new AuthenticationState ( authenticatedUser) ;
}
}
/// <summary>
/// 標記授權
/// </summary>
/// <param name="loginModel"></param>
/// <returns></returns>
public void MarkUserAsAuthenticated ( UserDto userDto)
{
HttpClient. DefaultRequestHeaders. Authorization = new AuthenticationHeaderValue ( "bearer" , userDto. Token) ;
UserName = userDto. Name;
//此處應該根據服務器的回傳的內容進行配置本地策略,作為演示,默認添加了“Admin”
var claims = new List < Claim > ( ) ;
claims. Add ( new Claim ( ClaimTypes. Name, userDto. Name) ) ;
claims. Add ( new Claim ( "Admin" , "Admin" ) ) ;
var authenticatedUser = new ClaimsPrincipal ( new ClaimsIdentity ( claims, "apiauth" ) ) ;
var authState = Task. FromResult ( new AuthenticationState ( authenticatedUser) ) ;
NotifyAuthenticationStateChanged ( authState) ;
//慈湖可以可以將Token存盤在本地存盤中,實作頁面重繪無需登錄
}
/// <summary>
/// 標記注銷
/// </summary>
public void MarkUserAsLoggedOut ( )
{
HttpClient. DefaultRequestHeaders. Authorization = null ;
UserName = null ;
var anonymousUser = new ClaimsPrincipal ( new ClaimsIdentity ( ) ) ;
var authState = Task. FromResult ( new AuthenticationState ( anonymousUser) ) ;
NotifyAuthenticationStateChanged ( authState) ;
}
}
NotifyAuthenticationStateChanged方法會通知身份驗證狀態資料(例如 AuthorizeView)使用者使用新資料重新呈現, HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);將HTTP請求頭中加入Token,這樣之后所有的請求都會帶上Token,
在Program中注入AuthProvider服務,以便于其他地方使用
//Program.cs
builder. Services. AddScoped < AuthenticationStateProvider , AuthProvider > ( ) ;
在Program中配置支持的策略
builder. Services. AddAuthorizationCore ( option = >
{
option. AddPolicy ( "Admin" , policy = > policy. RequireClaim ( "Admin" ) ) ;
} ) ;
登錄界面
添加Login.razor組件,代碼如下
< div style =" margin : 100px" >
< Spin Spinning = " isLoading" >
@if (model != null)
{
< Form OnFinish = " OnSave" Model = " @model" LabelCol = " new ColLayoutParam() {Span = 6 }" >
< FormItem Label = " 用戶名" >
< Input @bind-Value = " context.UserName" />
</ FormItem>
< FormItem Label = " 密碼" >
< Input @bind-Value = " context.Password" Type = " password" />
</ FormItem>
< FormItem WrapperColOffset = " 6" >
< Button Type = " @ButtonType.Primary" HtmlType = " submit" > 登錄</ Button>
</ FormItem>
</ Form>
}
</ Spin>
</ div>
public partial class Login
{
[ Inject ] public HttpClient Http { get ; set ; }
[ Inject ] public MessageService MsgSvr { get ; set ; }
[ Inject ] public AuthenticationStateProvider AuthProvider { get ; set ; }
LoginDto model = new LoginDto ( ) ;
bool isLoading;
async void OnLogin ( )
{
isLoading = true ;
var httpResponse = await Http. PostAsJsonAsync < LoginDto > ( $"api/Auth/Login" , model) ;
UserDto result = await httpResponse. Content. ReadFromJsonAsync < UserDto > ( ) ;
if ( string . IsNullOrWhiteSpace ( result? . Token) == false )
{
MsgSvr. Success ( $"登錄成功" ) ;
( ( AuthProvider) AuthProvider) . MarkUserAsAuthenticated ( result) ;
}
else
{
MsgSvr. Error ( $"用戶名或密碼錯誤" ) ;
}
isLoading = false ;
InvokeAsync ( StateHasChanged) ;
}
}
登錄界面代碼很簡單,就是向api/Auth/Login請求,根據回傳的結果判斷是否登入成功, ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);標記身份認證狀態已經修改,
修改布局
修改MainLayout.razor檔案
< CascadingAuthenticationState>
< AuthorizeView>
< Authorized>
< Layout>
< Sider Style =" overflow : auto; height : 100vh; position : fixed; left : 0; " >
< div class = " logo" >
進擊吧!Blazor!
</ div>
< Menu Theme = " MenuTheme.Dark" Mode = @MenuMode.Inline>
< MenuItem RouterLink = " /" >
主頁
</ MenuItem>
< MenuItem RouterLink = " /today" RouterMatch = " NavLinkMatch.Prefix" >
我的一天
</ MenuItem>
< MenuItem RouterLink = " /star" RouterMatch = " NavLinkMatch.Prefix" >
重要任務
</ MenuItem>
< MenuItem RouterLink = " /search" RouterMatch = " NavLinkMatch.Prefix" >
全部
</ MenuItem>
</ Menu>
</ Sider>
< Layout Class = " site-layout" >
@Body
</ Layout>
</ Layout>
</ Authorized>
< NotAuthorized>
< ToDo.Client.Pages.Login> </ ToDo.Client.Pages.Login>
</ NotAuthorized>
</ AuthorizeView>
</ CascadingAuthenticationState>
當授權通過后顯示<AuthorizeView>中<Authorized>的選單及主頁,反之顯示<NotAuthorized>的Login組件內容, 當需要根據權限顯示不同內容,可以使用<AuthorizeView>的Policy屬性實作,具體是在AuthenticationStateProvider中通過配置策略,比如示例中claims.Add(new Claim("Admin", "Admin"));就添加了Admin策略,在頁面上只需<AuthorizeView Policy="Admin">就可以控制只有Admin策略的賬戶顯示其內容了, CascadingAuthenticationState級聯身份狀態,它采用了Balzor組件中級聯機制,這樣我們可以在任意層級的組件中使用AuthorizeView來控制UI了 AuthorizeView 組件根據用戶是否獲得授權來選擇性地顯示 UI 內容, Authorized組件中的內容只有在獲得授權時顯示, NotAuthorized組件中的內容只有在未經授權時顯示,
修改_Imports.razor檔案,添加必要的參考
@using Microsoft. AspNetCore. Components. Authorization
運行查看效果
關于更多安全
安全是一個很大的話題,這個章節只是介紹了其最簡單的實作方式,還有更多內容推薦閱讀官方檔案:https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/?view=aspnetcore-5.0
次回預告
我們通過幾張圖表,將我們ToDo應用中任務情況做個完美統計,
回傳目錄