【问题标题】:How to trigger <Authorizing> state of <AuthorizeView> in Blazor WASM如何在 Blazor WASM 中触发 <AuthorizeView> 的 <Authorizing> 状态
【发布时间】:2021-07-28 19:18:30
【问题描述】:

鉴于此流程:

        <AuthorizeView>
            <Authorized>
                Welcome @context.User.FindFirst(ClaimTypes.GivenName)?.Value
                <NavLink class="nav-link" href="logout">
                    <span class="oi oi-account-logout"></span> Log Out
                </NavLink>
            </Authorized>
            <NotAuthorized>
                <Login/>
            </NotAuthorized>
            <Authorizing>
                <text>Please wait the system is authorizing your credentials</text>
            </Authorizing>
        </AuthorizeView>

在我的子类身份验证状态提供程序中,我正在这样做

//logged in successfully
NotifyAuthenticationStateChanged(Task.FromResult(new ClaimsPrincipal(
        new ClaimsIdentity(JwtParser.ParseClaimsFromJWT(token),
          "jwtAuthType"))));
//logged out
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))));

它们都工作正常。现在我想将状态设置为“授权”。我该怎么办?

official documentation 提到授权状态​​是有效状态,但没有明确说明如何触发它。

这样做:

//starting to log in
NotifyAuthenticationStateChanged(Task.FromResult<AuthenticationState>(null));

确实会触发 UI 中的“授权”状态,因此“空”AuthenticationState 似乎确实是 blazor 所期望的。但是,即使它按预期工作,它也会在前端引发异常。传递任何类型的 ClaimsPrincipal 会导致“已授权”或“未授权”,但绝不会“授权”。

这是该尝试触发的异常

blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
d.printErr  @   blazor.webassembly.js:1
d.preRun.push.window.Blazor._internal.dotNetCriticalError   @   blazor.webassembly.js:1
w   @   blazor.webassembly.js:1
_mono_wasm_invoke_js_blazor @   dotnet.5.0.5.js:1
do_icall    @   00aba242:0x194e4e
do_icall_wrapper    @   00aba242:0x79df9
interp_exec_method  @   00aba242:0x44ad3
interp_runtime_invoke   @   00aba242:0x12efff
mono_jit_runtime_invoke @   00aba242:0x118e5f
do_runtime_invoke   @   00aba242:0x79d42
mono_runtime_invoke_checked @   00aba242:0xf65d
mono_runtime_try_invoke_array   @   00aba242:0x10e831
ves_icall_InternalInvoke    @   00aba242:0xed492
ves_icall_InternalInvoke_raw    @   00aba242:0xecf57
do_icall    @   00aba242:0x194ddb
do_icall_wrapper    @   00aba242:0x79df9
interp_exec_method  @   00aba242:0x44ad3
interp_runtime_invoke   @   00aba242:0x12efff
mono_jit_runtime_invoke @   00aba242:0x118e5f
do_runtime_invoke   @   00aba242:0x79d42
mono_runtime_try_invoke @   00aba242:0x12982
mono_runtime_invoke @   00aba242:0x10ec2b
mono_wasm_invoke_method @   00aba242:0x108e48
Module._mono_wasm_invoke_method @   dotnet.5.0.5.js:1
call_method @   dotnet.5.0.5.js:1
(anonymous) @   dotnet.5.0.5.js:1
beginInvokeDotNetFromJS @   blazor.webassembly.js:1
h   @   blazor.webassembly.js:1
e.invokeMethodAsync @   blazor.webassembly.js:1
(anonymous) @   blazor.webassembly.js:1
invokeWhenHeapUnlocked  @   blazor.webassembly.js:1
(anonymous) @   blazor.webassembly.js:1
t.dispatchEvent @   blazor.webassembly.js:1
(anonymous) @   blazor.webassembly.js:1
(anonymous) @   blazor.webassembly.js:1
e.onGlobalEvent

我也试过

      var authenticatingStateTask = Task.FromResult(new AuthenticationState(null));
      NotifyAuthenticationStateChanged(authenticatingStateTask);

这一次甚至没有触发状态,遇到了不同的异常。

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Value cannot be null. (Parameter 'user')
System.ArgumentNullException: Value cannot be null. (Parameter 'user')
   at Microsoft.AspNetCore.Components.Authorization.AuthenticationState..ctor(ClaimsPrincipal user)
   at VisualService.Portal.Authentication.PortalAuthStateProvider.NotifyUserAuthenticating() in C:\projects\RemoteMonitoringNEW\EverythingIsInHere\SourceCode\ClientApps\VisualService.Portal\Authentication\PortalAuthStateProvider.cs:line 42
   at VisualService.Portal.Authentication.AuthenticationService.Login(String username, String password) in C:\projects\RemoteMonitoringNEW\EverythingIsInHere\SourceCode\ClientApps\VisualService.Portal\Authentication\AuthenticationService.cs:line 41
   at VisualService.Portal.Pages.Login.ExecuteLogin() in C:\projects\RemoteMonitoringNEW\EverythingIsInHere\SourceCode\ClientApps\VisualService.Portal\Pages\Login.razor:line 32
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
d.printErr @ blazor.webassembly.js:1
d.preRun.push.window.Blazor._internal.dotNetCriticalError @ blazor.webassembly.js:1
w @ blazor.webassembly.js:1
_mono_wasm_invoke_js_blazor @ dotnet.5.0.5.js:1
do_icall @ 00aba242:0x194e4e
do_icall_wrapper @ 00aba242:0x79df9
interp_exec_method @ 00aba242:0x44ad3
interp_runtime_invoke @ 00aba242:0x12efff
mono_jit_runtime_invoke @ 00aba242:0x118e5f
do_runtime_invoke @ 00aba242:0x79d42
mono_runtime_invoke_checked @ 00aba242:0xf65d
mono_runtime_try_invoke_array @ 00aba242:0x10e831
ves_icall_InternalInvoke @ 00aba242:0xed492
ves_icall_InternalInvoke_raw @ 00aba242:0xecf57
do_icall @ 00aba242:0x194ddb
do_icall_wrapper @ 00aba242:0x79df9
interp_exec_method @ 00aba242:0x44ad3
interp_runtime_invoke @ 00aba242:0x12efff
mono_jit_runtime_invoke @ 00aba242:0x118e5f
do_runtime_invoke @ 00aba242:0x79d42
mono_runtime_try_invoke @ 00aba242:0x12982
mono_runtime_invoke @ 00aba242:0x10ec2b
mono_wasm_invoke_method @ 00aba242:0x108e48
Module._mono_wasm_invoke_method @ dotnet.5.0.5.js:1
call_method @ dotnet.5.0.5.js:1
(anonymous) @ dotnet.5.0.5.js:1
beginInvokeDotNetFromJS @ blazor.webassembly.js:1
h @ blazor.webassembly.js:1
e.invokeMethodAsync @ blazor.webassembly.js:1
(anonymous) @ blazor.webassembly.js:1
invokeWhenHeapUnlocked @ blazor.webassembly.js:1
(anonymous) @ blazor.webassembly.js:1
t.dispatchEvent @ blazor.webassembly.js:1
(anonymous) @ blazor.webassembly.js:1
(anonymous) @ blazor.webassembly.js:1
e.onGlobalEvent @ blazor.webassembly.js:1
Show 6 more frames

【问题讨论】:

    标签: c# authentication .net-5 blazor-webassembly


    【解决方案1】:

    首先:

    没有像我使用的 udemy 课程那样解释得很好。这是link to Felipe Gavilan's youtube video,其中解释了标签以及如何触发和测试它。我的答案并不是那么松散地基于他的代码。 Felipe Gavilan 的视频是西班牙语的,但代码还是英文的,所以我相信你可以跟上。

    This youtube video by Patrick God 是对您遇到问题的授权逻辑的完整解释,但他没有触及您问题的重点。

    他们都有我强烈推荐的udemy课程。

    这个答案中的代码是我对他们两种方法的混合实现。

    也就是说,我们开始吧:

    我是如何做到的以及如何测试:

    1 依赖和服务

    安装 nuget 包并将它们添加到客户端项目中的 _Imports.razor。

    @using Microsoft.AspNetCore.Components.Authorization
    @using Microsoft.AspNetCore.Authorization
    

    将服务添加到 Program.cs 中的 ConfigureServices(...) 方法

    private static void ConfigureServices(IServiceCollection services){
               
                services.AddAuthorizationCore();
                services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>(); //see point 2
    ...
    }
    

    2 种方法

    现在,自定义身份验证状态提供程序方法可以满足您的要求:

    public class CustomAuthStateProvider : AuthenticationStateProvider
    {
    
        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            await Task.Delay(2000); //USE THIS DELAY TO TEST!
            var identity = new ClaimsIdentity();
            var user = new ClaimsPrincipal(identity);
            var state = new AuthenticationState(user);
            NotifyAuthenticationStateChanged(Task.FromResult(state));
            return state;
         }
    ...
    }
    

    3 个前端组件

    在 app.razor 中,我只是这样布置骨架:

    <Found Context="routeData">
        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
            <Authorizing>
                <span>Espere mientras se revisan las credenciales de acceso...</span>
            </Authorizing>
            <NotAuthorized>
                <Login />
            </NotAuthorized>
        </AuthorizeRouteView>
    </Found>
    <NotFound>
        <CascadingAuthenticationState>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Lo siento, no hay nada en esta direcci&oacute;n</p>
            </LayoutView>
        </CascadingAuthenticationState>
    </NotFound>
    

    --->这足以触发&lt;Authorizing&gt;状态。

    但是,您现在想知道如何使用 jwt 将其扩展到登录和注销方法:

    如果可以的话,去看看我指出的udemy课程,或者:

    我使用 LocalStorageService 来存储令牌并在全球范围内访问它(在许多教程中都将其视为标准,尽管我确信其他人会对此发表评论并证明我错了)。

    GetAuthenticationStateAsync() 方法如下所示:

    public class CustomAuthStateProvider : AuthenticationStateProvider
        {
            private readonly ILocalStorageService _localStorageService;
            private readonly HttpClient _http;
    
            public CustomAuthStateProvider(ILocalStorageService localStorageService, HttpClient http)
            {
                _localStorageService = localStorageService;
                _http = http;
            }
    
            public override async Task<AuthenticationState> GetAuthenticationStateAsync()
            {
                await Task.Delay(2000); //I kept this, yeah.
                string authToken = await _localStorageService.GetItemAsStringAsync("authToken");
                var identity = new ClaimsIdentity(); 
    
                _http.DefaultRequestHeaders.Authorization = null;
                if(!string.IsNullOrEmpty(authToken))
                {
                    identity = new ClaimsIdentity(ParseClaimsFromJwt(authToken),"jwt");
                    _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
                }
                var user = new ClaimsPrincipal(identity);
                var state = new AuthenticationState(user);
    
                NotifyAuthenticationStateChanged(Task.FromResult(state));
                return state;
            }
    

    这有一个非常优雅的优势:

    登录方法可以简单地类似于:

    public async void Login(string username, string password)
    {
        bool validLogin = goToDataBaseToCheckUser(username,password);
        if(validLogin)
        {
            string token = CreateToken(user);
            await LocalStorage.SetItemAsync<string>("authToken", token);
            var authstate = await AuthStateProvider.GetAuthenticationStateAsync();
        }
        else
        {
            // ¯\_ (ツ)_/¯ idk, show a message or something.
        }
    }
    

    然后退出:

    private async void Logout()
    {
        await LocalStorage.RemoveItemAsync("authToken");
        await AuthStateProvider.GetAuthenticationStateAsync();
    }
    

    这会将NotifyAuthenticationStateChanged() 调用留在一个地方,让您可以通过令牌控制应用程序,并保持方法调用的可读性,就像它们应该的那样。

    我希望这会有所帮助。

    嘿嘿。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-07
      • 2021-12-07
      • 2022-01-10
      • 2021-12-04
      • 1970-01-01
      • 1970-01-01
      • 2021-07-16
      • 2020-09-06
      相关资源
      最近更新 更多