Blazor Server基于SignalR,建立连接之后可以通过cookie进行验证,配合
Asp.Net Core Identity
使用。
Blazor WebAssembly 应用的保护方式与单页应用 (SPA) 相同。 可通过多种方式向 SPA 进行用户身份验证,但最常用、最全面的方式是使用基于 OAuth 2.0 协议的实现,例如 OpenID Connect (OIDC)。
如果需要使用OIDC对应用进行身份验证和授权,需要安装在wasm里安装Nuget包
Microsoft.AspNetCore.Components.WebAssembly.Authentication
。
(安装的前提是你的blazor项目需要用
aps.net core
作为host。这个包用于处理基础身份验证协议,建立在
oidc-client.js
库基础之上。)
当然除了使用OIDC进行验证和授权之外,还可以使用SameSite cookie等。但是blazor wasm的设计上就决定了使用OAuth和OIDC是进行身份验证过的最佳选择。出于以下几个原因,我们这里使用JWT(Json Web Token)进行身份验证而不是使用cookie:
-
可以减小攻击面,因为并非所有请求中都会发送令牌
-
服务器终结点不要求针对跨站点请求伪造 (CSRF) 进行保护,因为会显式发送令牌。所以可将 Blazor WebAssembly 应用与 MVC 或 Razor Pages 应用一起托管
-
令牌的权限比 cookie 窄。例如,令牌不能用于管理用户帐户或更改用户密码
-
令牌的生命周期更短(默认为一小时),这限制了攻击时间窗口。还可随时撤销令牌。
JWT分为三部分
{header}.{payload}.{signature}
, 解码之后各部分格式和含义如下:
"typ"
:
"JWT"
,
"alg"
:
"RS256"
,
"kid"
:
"X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}
.
{
"exp"
:
1610059429
,
"nbf"
:
1610055829
,
"ver"
:
"1.0"
,
"iss"
:
"https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/"
,
"sub"
:
"5ee963fb-24d6-4d72-a1b6-889c6e2c7438"
,
"aud"
:
"70bde375-fce3-4b82-984a-b247d823a03f"
,
"nonce"
:
"b2641f54-8dc4-42ca-97ea-7f12ff4af871"
,
"iat"
:
1610055829
,
"auth_time"
:
1610055822
,
"idp"
:
"idp.com"
,
"tfp"
:
"B2C_1_signupsignin"
,
}
.
[
Signature
]
字段
|
说明
|
typ
|
token的类型:jwt
|
alg
|
所使用的加密算法
|
kid
|
秘钥序,开发人员可以用它标识认证token的某一秘钥
|
exp
|
过期时间
|
nbf
|
在这个时间点之前,jwt都是不可用的
|
ver
|
版本
|
iss
|
jwt的签发者
|
sub
|
jwt所面向的用户
|
aud
|
接收jwt的一方
|
nonce
|
|
iat
|
jwt签发时间
|
auth_time
|
|
idp
|
|
tfp
|
|
jti
|
jwt的唯一身份标识,主要用来作为一次性token,回避重放攻击
|
请先新建一个带有认证功能的Blazor WebAssembly项目便于接下来的理解,该模板的host项目已经添加了IdentityServer服务
验证流程:
-
当未登陆的用户点击了登陆按钮或者请求到应用了
[Authorize]
特性的页面上时,就会将该用户重定向到
/authentication/login
。
-
在登陆页上,身份验证库
Microsoft.AspNetCore.Components.WebAssembly.Authentication
将会把请求重定向到授权服务上(接下来的文章中会介绍如何使用IdentityServer搭建授权服务)。该授权服务负责确定用户是否通过身份验证,并发送token作为响应。
-
如果用户未通过身份验证,则会提示让用户进行登录。此处可以配合
ASP.NET Core Identity
使用
-
如果用户已通过身份验证,则授权服务生成相应的token,并将浏览器重定向到
/authentication/login-callback
。
-
当Blazor应用加载
/authentication/login-callback
时,就处理了身份验证相应。
-
如果身份验证成功,则可以选择将用户重定向到原始访问的url上。
-
如果因为任何原因验证失败,则会将用户重定向到
authentication/login-failed
,并显示错误。
总结:整个工作流程涉及到三个url,这三个其实都在
Shared/Authentication.razor
里。:
-
/authentication/login
页
-
/authentication/login-callback
-
/authentication/login-failed
该nuget包用下表中显示的路由表示不同的身份验证状态。
路由
|
目标
|
authentication/login
|
触发登录操作。
|
authentication/login-callback
|
处理任何登录操作的结果。
|
authentication/login-failed
|
当登录操作由于某种原因失败时显示错误消息。
|
authentication/logout
|
触发注销操作。
|
authentication/logout-callback
|
处理注销操作的结果。
|
authentication/logout-failed
|
当注销操作由于某种原因失败时显示错误消息。
|
authentication/logged-out
|
指示用户已成功注销。
|
authentication/profile
|
触发操作以编辑用户配置文件。
|
authentication/register
|
触发操作以注册新用户。
|
对用户验证通过之后,就需要验证授权规则来控制用户可以具体执行哪些操作。常见的授权规则有以下几种:
-
只要用户通过验证就授权
-
基于角色的授权
-
基于用户claim的授权
-
基于策略的授权
在WebAssembly项目中(非host),使用
Microsoft.AspNetCore.Components.WebAssembly.Authentication
包提供的
AddOidcAuthentication
方法在服务容器中注册用户身份验证支持。这里以Google的OIDC服务为例进行配置:
builder.Services.AddOidcAuthentication(options =>
builder.Configuration.Bind("Local", options.ProviderOptions);
});
appsettings.json Local配置项:
"Local": {
"Authority": "https://accounts.google.com/",
"ClientId": "2.......7-e.....................q.apps.googleusercontent.com",
"PostLogoutRedirectUri": "https://localhost:5001/authentication/logout-callback",
"RedirectUri": "https://localhost:5001/authentication/login-callback",
"ResponseType": "id_token"
-
在_Imports.razor
里添加@using Microsoft.AspNetCore.Components.Authorization
。
-
在wwwroot/index.html
里添加<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/ AuthenticationService.js"></script>
。这个js用来处理OIDC的细节,应用内部会调用这个服务。
CascadingAuthenticationState
组件:用来提供经过验证的用户的信息。AuthorizaRouteView
组件:用来确保用户是否可以访问给定的页面,如果未被授权则渲染RedirectToLogin
组件。RedirectToLogin
组件:用来将用户重定向到登录页。
整体代码如下:
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@if (!context.User.Identity.IsAuthenticated)
<RedirectToLogin />
You are not authorized to access
this resource.
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
Shared/RedirectToLogin.razor
组件,用来引导用户进行登录,传入验证成功之后的跳转页。
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
protected override void OnInitialized()
Navigation.NavigateTo(
$"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
Shared/LoginDisplay.razor
组件。对于验证过的用户来说显示用户名等信息,并提供注销功能。对于未验证的用户则提供登录功能。
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity.Name!
<button class="nav-link btn btn-link" @onclick="BeginSignOut">
Log out
</button>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code {
private async Task BeginSignOut(MouseEventArgs args)
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
Pages/Authentication.razor
下的RemoteAuthenticatorView
(属于nuget包),用来处理不同的验证步骤。
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code {
[Parameter]
public string Action { get; set; }
该组件可以根据用户是否经过授权来显示不同的UI,常用来设置页面内某个部分是否可见。该组件公开了一个AuthenticationState
类型的变量context
,可以使用该变量获取已登录的用户信息。
<AuthorizeView>
<Authorized>
<h1>Hello, @context.User.Identity.Name!</h1>
<p>You can only see this content if you're authorized.</p>
<button @onclick="SecureMethod">Authorized Only Button</button>
</Authorized>
<NotAuthorized>
<h1>Authentication Failure!</h1>
<p>You're not signed in.</p>
</NotAuthorized>
<Authorizing>
<h1>Authentication in progress</h1>
<p>You can only see this content while authentication is in progress.</p>
</Authorizing>
</AuthorizeView>
@code {
private void SecureMethod() { ... }
如果未指定授权规则,则表示用户验证通过就表示已授权。
AuthorizeView
支持基于角色或基于策略的授权。配置如下:
<AuthorizeView Roles="admin, superuser">
<p>You can only see this if you're an admin or superuser.</p>
</AuthorizeView>
<AuthorizeView Policy="content-editor">
<p>You can only see this if you satisfy the "content-editor" policy.</p>
</AuthorizeView>
基于策略的授权包含一个特例,即基于claim的授权。例如,可以定义一个要求用户具有某种claim的策略
AuthenticationStateProvider
是AuthorizeView
和CascadingAuthenticationState
组件用于获取身份验证状态的基础服务。我们一般不直接使用这个,主要是因为当基础身份验证状态发生改变时不会自动通知UI组件。
可以通过AuthenticationStateProvider
服务来获取用户的Claim
数据:
@page "/"
@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
<h3>ClaimsPrincipal Data</h3>
<button @onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>
<p>@_authMessage</p>
@if (_claims.Count() > 0)
@foreach (var claim in _claims)
<li>@claim.Type: @claim.Value</li>
<p>@_surnameMessage</p>
@code {
private string _authMessage;
private string _surnameMessage;
private IEnumerable<Claim> _claims = Enumerable.Empty<Claim>();
private async Task GetClaimsPrincipalData()
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
_authMessage = $"{user.Identity.Name} is authenticated.";
_claims = user.Claims;
_surnameMessage =
$"Surname: {user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value}";
_authMessage = "The user is NOT authenticated.";
刚才我们说了一般不要直接使用AuthenticationStateProvider
,但是如果真要在页面中获取验证状态该怎么办?
答案就是定义一个Task<AuthenticationState>
类型的级联参数,父级的AuthorizeRouteView
或CascadingAuthenticationState
组件,会给这个参数赋值。反过来CascadingAuthenticationState
会从AuthenticationStateProvider
服务接收这个参数 。
@page "/"
<button @onclick="LogUsername">Log username</button>
<p>@_authMessage</p>
@code {
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private string _authMessage;
private async Task LogUsername()
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
_authMessage = $"{user.Identity.Name} is authenticated.";
_authMessage = "The user is NOT authenticated.";
然后在配置依赖注入:
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
重写GetAuthenticationStateAsync
方法即可:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class CustomAuthStateProvider : AuthenticationStateProvider
public override Task<AuthenticationState> GetAuthenticationStateAsync()
var identity = new ClaimsIdentity(new[]
new Claim(ClaimTypes.Name, "mrfibuli"),
}, "Fake authentication type");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
然后配置依赖注入
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
表示用户经过授权之后才可以访问页面,与AuthorizeView
区别是一个属于页面级,一个属于UI块。
- 对所有的页面都需要进行授权:在
_Imports.razor
文件中使用Authorize特性。
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
- 对某个页面进行授权:在页面上添加
@attribute [Authorize]
。
基于角色和策略的授权:
@attribute [Authorize(Roles = "admin, superuser")]
@attribute [Authorize(Policy = "content-editor")]
- 不可以在Blaozr WASM应用中保存刷新令牌。只能在托管的host里保存。
Access Token
的作用域:验证库默认会添加openid
和profile
这两个scope。如果需要添加其它的scope可以调用:
builder.Services.AddOidcAuthentication(options =>
...
options.ProviderOptions.DefaultScopes.Add("ScopeXXXXX");
});
下一篇将会介绍如何与IdentityServer集成。
- https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/webassembly/?view=aspnetcore-5.0#authentication-component
- https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-5.0&tabs=visual-studio
- https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/?view=aspnetcore-5.0#authorizeview-component
将Blazor部署到GitHub Pages
该通过gh-pages分支将存储库中的Blazor WebAssembly项目发布并部署到 。 可以在示例中看到它。
这将自动执行所有必需的设置,包括添加.nojekyll文件和404.html页面重定向解决方法,这对于使单页面应用程序在GitHub Pages中工作是必需的。
这基于的示例。
- uses : proulxsamuel/deploy-blazor-to-gh-pages@master
with :
# The path to the Blazor project in the repository.
# Default: '.'
project_path : ' '
这要求您在存储库设置中启用GitHub Pages,并首先通过存储库。
您可以查看工作流程示例。
之前用 Blazor 做了几个小应用,但是一直没有去做身份验证系统,之前也没有ASP.NET的基础,C#也是最近学的,而且重要的是,网上居然几乎没有相关的资料,于是咱就结合网上的一些信息和微软的官方文档(微软的文档是我见过最好的文档了)做了一个身份鉴权的小Demo。在 Provider 下写自定义的基础认证服务,这里使用的session的形式做鉴权认证。Blazor 是微软的新型的web开发方案,用来做全栈开发真是太爽了。然后在项目的目录下创建Auth目录存放鉴权的基础设施。一个简单的登录页面示例;
如何使用这个包
对于 v0.*.* 很重要: 此包仍处于预发布阶段,因此版本控制不符合语义版本控制。 功能和错误修复会增加补丁版本,重大更改会增加次要版本。 因此,在次要版本之间升级之前,请务必查看发行说明。
这个包导入了另外两个包,它们是:
- 为 WebExtensions 标准 API 提供互操作。
Blazor.BrowserExtension.Build(在此存储库中)- 向项目添加构建目标和任务。
创建新项目
运行dotnet new --install Blazor.BrowserExtension.Template 。
运行dotnet new browserext --name <ProjectName>以使用模板初始化新项目。
将工作目录更改为新创建的
Blazo的身份认证授权示例以及如何对接统一登录平台
在阅读本文之前,希望您已经对ASP.NET Core或者Web应用的身份认证机制有所了解。本文主要讲述Blazor WebAssembly模式下的身份认证和授权方法,以及页面元素访问控制,假如您对ASP.NET Core身份认证机制还不了解,可以查看该链接获取完整的sso——统一登录示例。
AuthorizeView 组件
AuthorizeView是blazor中用于身份认证的内置组件。
<AuthorizeView>
注意:Key要求有一定的长度,建议复制新生成的Guid.NewGuid().ToString();上篇已经实现了登录与退出的主服务类,其他还需要一些具体的东西。1、引用Blazored.LocalStorage包;7、登录Razor组件(略)。注意:需要注册服务。...
本文要点WebAssembly 是一种新的客户端技术,可以在所有现代浏览器(包括移动浏览器)中实现近乎原生的性能,而且不需要插件。许多语言,包括 C、C#、Go 和 Ru...
Blazor WebAssembly
Blazor WebAssembly 应用在客户端上运行。 由于用户可绕过客户端检查,因为用户可修改所有客户端代码, 因此授权仅用于确定要显示的 UI 选项,所有客户端应用程序技术都是如此。
Blazor Server
Blazor Server应用通过使用 SignalR 创建的实时连接运行。 建立连接后,将处理基于 Sig
通过前面的ASP.NET Core Blazor编程系列文章为读者介绍了Blazor及组件的相关基础概念,以及我们已经实现了用Blazor实现对数据的增删改查这四大基本功能,以及文件上传的功能,通过这些功能的实现我们已经能用Blazor处理一些简单的实际问题,特别是企业内部信息管理系统的相关问题。前面的ASP.NET Core Blazor编程系列文章中却没有讲到信息管理系统中一个最基本功能——登录,有关登录功能的介绍。
通过使用客户端和服务器上的 .NET,你可以轻松共享代码,并使用一组一致的语言、框架和工具生成应用。生成解决方案后,Blazor 应用中已生成的静态文件由已设置回退路由的 ASP.NET Core 应用托管。下载的程序集是普通的 .NET 程序集,就像在任何其他 .NET 应用中使用的程序集一样。到应用的某些部分的深层链接通常需要服务器上的路由解决方案。Blazor 组件根据呈现的输出的应用方式进行去耦。在 Blazor Server 应用中,组件在服务器上运行,而不是在浏览器中的客户端运行。
在Blazor WebAssembly开发模式下, 浏览器一般需要下载多达10M的资源, 主要是 .net 的dll 文件, 即使是在企业局域网内访问速度也不快. 发布模式可以明显减小资源文件的大小.
这里介绍使用官方提供的 blazor-devserver.exe, 作为企业内的部署服务器, 主要优点有:
⒈ 使用非常方便
2. 支持url rewrite, 以模版项目为例, counter...
本文将从0开始介绍如何搭建一个适用于Blazor WASM应用的且基于OpenID和OAuth2.0的认证授权服务。我们会从创建空白项目一步一步开始,让大家了解到整个搭建流程,没有直接使用微软给定的认证模板或者IdentityServer的UI模板。
- 前端使用的是Blazor WebAssembly最新版本(基于 .net 5.0),主要我认为相对于Blazor Server来说,这个才是Web的未来。而且可以充分开会客户机的性能。
- 认证服务使用的Duende.IdentityServer
Blazor页面元素授权——AuthorizeView 组件的使用
上篇博客我们说到了blazor的身份认证的实现,对于AuthorizeView 组件来说,可以通过级联参数来获取包含了用户信息的AuthenticationState对象。
请注意,你需要引用Microsoft.AspNetCore.Components.Authorization Nuget包,并且在启动类中添加服务Services.AddAuthorizationCore();
使用CascadingAuthenticationStat