前面有说到UserAuthentication()
跟UserAuthorization()
,这两个的差别在于:前者用于验证登录者是谁,后者则决定登录者可以做什么。
举例来说,一个员工要登录员工系统,他必须输入帐号(如员工 ID、姓名或是 email)、密码,系统才能知道是谁登录了,这就是Authentication(验证)
,处理Authentication
的方式有Cookie
、Token
、第三方验证(OAuth 或API-token)
、OpenId
及SAML
。
而当员工登录系统后,一般员工通常不会有跨部门或是管理权限,他只能看到他自己或所属部门的信息,例如生产部员工看不到会计部的财务,不过会计部为了计算成本却看得到生产部的原料价格,这就是Authorization (授权)
。
在决定登录者可以做什么前,必须先知道登录者是谁,所以UserAuthentication()
必须放在UserAuthorization()
前面。
ASP.NET Core Identity
使用的是基于Claim
的验证,要了解Claim
,必须先了解Claim
、ClaimsIdentity
跟ClaimsPrincipal
是什么。
Claim
就是关于使用者的一些信息,Claim Type
跟Claim Value
(可以不用给)就组成一个Claim
,Claim
可以是姓名
、电话
、角色
、Email
甚至是角色
等等。Authorization
就是用Claim
判断使用者有无授权。
new System.Security.Claims.Claim(ClaimTypes.Role, role.Name)
ClaimsIdentity
则是多个Claim
的集合,像是驾照
上面记录了姓名
、生日
、电话
,驾照
就是一个ClaimsIdentity
。
ClaimsPrincipal
是多个ClaimsIdentity
的集合,台湾人都有身分证跟健保卡,有些人还有驾照,身分证、健保卡跟驾照都是ClaimsIdentity
,持有它们的人就是ClaimsPrincipal
。
而每一个HTTP request
都会产生HttpContext
对象,该对象就存有目前request
的信息,其下可以找到一个类型为ClaimsPrincipal
的 Property 名为User
,这个 Property 就是由UseAuthentication()
引入的Authentication
Middleware 产生的。
登录机制可以用Cookie
或是JWT
实现,但Authentication
Middleware 怎么知道要用哪个方法产生User
Property?那就要看Authentication
Scheme 跟Authentication
Handlers。
Authentication Handlers
Authentication Handlers
就是处理验证的方式,ASP.NET Core Identity
可以调用AuthenticateAsync()
API 去验证使用者已登录,如验证失败就调用ChallengeAsync()
将使用者导回登录页面
,如授权失败则用ForbidAsync()
禁止使用者访问,当然也可以自己实现这些行为。下面例子中用了JWT
跟Cookie
的验证方式,如果用了前者,就必须验证JWT token
并产生ClaimsPrincipal
回传到HttpContext.User
中;使用后者则会检查当前request
的cookie
并产生ClaimsPrincipal
。
builder.Services.AddAuthentication()
.AddJwtBearer()
.AddCookie();
Authentication Scheme
用了任何一种方式注册Authentication Handlers
就称为Authentication Scheme
,每个Authentication Scheme
都有一个独特的名字以识别,且可以自己设定Authentication Handlers
,下面的程序结果跟上面会是一样,因为它们都有预设的Scheme
Name。
builder.Services.AddAuthentication()
.AddJwtBearer("Bearer")
.AddCookie("Cookies");
Blazor Authentication
Blazor
用的验证方式跟ASP.NET Core
一样,不过Blazor WebAssembly
跟Blazor Server
又有不同,前者的验证就像任何前端网站一样可以被绕过,因为使用者一端的程序可以被使用者改动,因此发送数据的API
端一定也需要验证;后者则可用内建的AuthenticationStateProvider
取得前面说的HttpContext.User
,笔者此前就是自己继承并重写这项Service
实现JWT
验证的。
AuthenticationStateProvider
就是昨天说到的<AuthorizeView>
及<CascadingAuthenticationState>
可以取得当前验证状态的原因,但如果没有要重写预设验证机制的话,建议不要自己在Component
注入一个AuthenticationStateProvider
出来,直接使用的缺点很明显,若当前request
的验证状态有改动,因为你改动了这个Component
的验证机制,该Component
就不会被告知。
可以看到下图,ApiAuthenticationStateProvider
继承了AuthenticationStateProvider
,并重写了Task<AuthenticationState>
,这个 Property 可以取得当前的验证状态,MarkUserAsAuthenticated()
跟MarkUserAsLoggedOut()
则是笔者自己写的方法用以标示使用者通过验证及注销系统,NotifyAuthenticationStateChanged()
顾名思义会通知各个 Component 当前验证状态,这就是自己继承并重写的案例。
下图则是在 Component 取得当前request
的HttpContext.User
及Claims
的作法,不过这里是先利用服务取得User
再取得其下Claims
,其实是多此一举了。
如果只是要取得HttpContext.User
,只要如下图般就可以了,因为 Task<AuthenticationState>
会以[CascadingParameter]
的方式层层传递下去。
引用:
注:本文代码通过 .NET 6 + Visual Studio 2022 重构,可点击原文链接与重构后代码比较学习,谢谢阅读,支持原作者