目前尚未发现有任意一篇博文详细介绍了Blazor的认证系统,基本上都是东拼西凑的,很难受,给新手带来了极大的困惑。这里介绍一个较为简单的认证方案,其实如果放在Vue+PHP/JAVA的组合的话,这个是完全没有一点坑的,只不过由于Blazor的相关资料较少,所以遇到的坑也比较多。
首先,我们要知道,一套认证系统首先需要包括至少以下内容:
- 登录/注册页面
- 登录/注册接口
- 认证中间件
其中,认证中间件有微软实现的Microsoft.AspNetCore.Components.Authorization和Microsoft.AspNetCore.Authorization库,无需再去单独实现(除非你的需求极为复杂);剩下两个如果你真的觉得IdentityServer4适合你的话,也可以用,但是据我了解模板什么的比较难定制,难看也得忍着,显得跟主要UI十分割裂,感官上不好。所以这里我们登录/注册使用自己编写的页面和接口,其中页面在Client项目中编写,接口在Server项目中编写。
由于每个人的具体实现不一样,这里只列出非常关键的配置,剩下的请自行思考。
Program.cs on Client
我们主要配置HttpClient和Authorization,HttpClient可以这么配置:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
一般项目也会给你配好,如果没有就自己加上。AddHttpClient是个工厂模式的扩展,这里有个比较有意思的用法就是写一个Service层,然后通过AddHttpClient来注入HttpClient,这样可以分几个配置来使用HttpClient。例如:
builder.Services.AddHttpClient<AuthHttpService>()
.ConfigureHttpClient(config =>
{
config.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
这里就是将AuthHttpService注入并实例化,在razor页面就可以用注入的方式使用了:
@inject AuthHttpService AuthHttpService
如果学过Spring框架,应该也比较熟悉类似的配置,理解起来会更快些。没有接触过也可以看一下我的解释:
首先AddHttpClient根据传入的泛型确定类,然后进行实例化,在构造函数中注入准备好的HttpClient对象。与此同时,通过ConfigureHttpClient配置HttpClient的一些信息,也会更改该HttpClient对象。不过,其他HttpClient对象是互不影响的,因此每一个构建的示例都是相对独立的。至于依赖注入,建议自行了解,在Blazor/.Net Core这里,依赖注入只在构造方法完成(也就是想要注入啥对象,往构造方法的参数写就完事了,前提是该类型注册了DI注入,这个我们后面再提)。
然后还有一个小技巧,如果想要对HttpClient的Request/Response“做些手脚”,例如在Header加个Authorization,就可以通过AddHttpMessageHandler方法来加一个DelegatingHandler,以此达到类似于“请求/响应中间件”的效果,与axios那边的原理差不多。由于我的实现比较简单,就大概分享一下给大家,可以照葫芦画瓢做一下。在Program.cs中加入:
builder.Services.AddTransient<IdentityHandler>(); // 这里是将IdentityHandler在DI中注册了,如果不注册的话,AddHttpMessageHandler用不了
builder.Services.AddHttpClient<UserHttpService>()
.ConfigureHttpClient(config =>
{
config.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
})
.AddHttpMessageHandler<IdentityHandler>();
IdentityHandler的实现如下:
using Blazored.LocalStorage;
using System.Net.Http.Headers;
namespace Cytus2ScoreManager.Client.Utils
{
public class IdentityHandler : DelegatingHandler
{
private readonly ILocalStorageService _localStorageService;
private readonly ILogger<IdentityHandler> _logger;
public IdentityHandler(ILocalStorageService localStorageService, ILogger<IdentityHandler> logger)
{
_localStorageService = localStorageService;
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage httpResponseMessage;
try
{
string accessToken = await _localStorageService.GetItemAsStringAsync("auth-token");
if (string.IsNullOrEmpty(accessToken))
{
throw new Exception($"Access token is missing for the request {request.RequestUri}");
}
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
httpResponseMessage = await base.SendAsync(request, cancellationToken);
httpResponseMessage.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to run http query {RequestUri}", request.RequestUri);
throw;
}
return httpResponseMessage;
}
}
}
这里实现了读取localstorage中auth-token的值并加入到Header的Authorization中,如果需要更多定制也可以自己加。Localstorage的库怎么用,自己搜Blazored.LocalStorage。
最后在Program.cs中,加入以下代码来实现认证功能。
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();