【问题标题】:Custom AuthenticationStateProvider Breaking Azure MSAL authentication in Blazor自定义 AuthenticationStateProvider 在 Blazor 中破坏 Azure MSAL 身份验证
【发布时间】:2021-11-17 09:34:09
【问题描述】:

我有一个 Blazor Web 程序集应用程序。 它具有 Azure AD 身份验证来验证页面和 API - 有效 它具有 sql JWS 令牌身份验证来验证页面和 API - 有效

问题是当我尝试同时启用它们时。 我需要将自定义 AuthenticationStateProvider 范围添加到客户端上的 program.cs 以进行 JWS 令牌身份验证,当我尝试使用 Azure 身份验证ncaught (in promise) Error: System.ArgumentException: There is no event handler associated with this event. EventId: '62'. (Parameter 'eventHandlerId') at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs) at Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs) at Microsoft.AspNetCore.Components.WebAssembly.Infrastructure.JSInteropMethods.DispatchEvent(WebEventDescriptor eventDescriptor, String eventArgsJson) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) --- End of stack trace from previous location --- at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.InvokeSynchronously(JSRuntime jsRuntime, DotNetInvocationInfo& callInfo, IDotNetObjectReference objectReference, String argsJson) at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.BeginInvokeDotNet(JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, String argsJson) at Object.endInvokeDotNetFromJS (https://localhost:5001/_framework/blazor.webassembly.js:1:4191) at Object.invokeJSFromDotNet (https://localhost:5001/_framework/blazor.webassembly.js:1:3797) at Object.w [as invokeJSFromDotNet] (https://localhost:5001/_framework/blazor.webassembly.js:1:64301) at _mono_wasm_invoke_js_blazor (https://localhost:5001/_framework/dotnet.5.0.9.js:1:190800) at do_icall (<anonymous>:wasm-function[10596]:0x194e4e) at do_icall_wrapper (<anonymous>:wasm-function[3305]:0x79df9) at interp_exec_method (<anonymous>:wasm-function[2155]:0x44ad3) at interp_runtime_invoke (<anonymous>:wasm-function[7862]:0x12efff) at mono_jit_runtime_invoke (<anonymous>:wasm-function[7347]:0x118e5f) at do_runtime_invoke (<anonymous>:wasm-function[3304]:0x79d42) 登录时收到此错误

这是我的自定义 AuthenticationStateProvider

public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILocalStorageService _localStorage;

    public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
    {
        _httpClient = httpClient;
        _localStorage = localStorage;
    }
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var savedToken = await _localStorage.GetItemAsync<string>("authToken");

        if (string.IsNullOrWhiteSpace(savedToken))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);

        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
    }

    public void MarkUserAsAuthenticated(string email)
    {
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
        NotifyAuthenticationStateChanged(authState);
    }

    public void MarkUserAsLoggedOut()
    {
        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(anonymousUser));
        NotifyAuthenticationStateChanged(authState);
    }

    private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
    {
        var claims = new List<Claim>();
        var payload = jwt.Split('.')[1];
        var jsonBytes = ParseBase64WithoutPadding(payload);
        var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);

        keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

        if (roles != null)
        {
            if (roles.ToString().Trim().StartsWith("["))
            {
                var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());

                foreach (var parsedRole in parsedRoles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, parsedRole));
                }
            }
            else
            {
                claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
            }

            keyValuePairs.Remove(ClaimTypes.Role);
        }

        claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));

        return claims;
    }

    private byte[] ParseBase64WithoutPadding(string base64)
    {
        switch (base64.Length % 4)
        {
            case 2: base64 += "=="; break;
            case 3: base64 += "="; break;
        }
        return Convert.FromBase64String(base64);
    }

 
}

}

我将其添加到 program.cs 中

 builder.Services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProviderClient>();

并使用此帐户身份验证服务触发它

 public interface IAccountService
{
    User User { get; }
    Task Initialize();
    Task<LoginResult> Login(LoginRequest model);
    Task Logout();
}

public class AccountService : IAccountService
{
   // private IHttpService _httpService;
    private NavigationManager _navigationManager;
  //  private ILocalStorageService _localStorageService;
    private string _userKey = "user";
    private readonly HttpClient _httpService;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    private readonly ILocalStorageService _localStorage;

    public User User { get; private set; }

    public AccountService(
        HttpClient httpService,
        AuthenticationStateProvider authenticationStateProvider,
        ILocalStorageService localStorageService
    ) {
        _httpService = httpService;
        _authenticationStateProvider = authenticationStateProvider;
        _localStorage = localStorageService;
    }

    public async Task Initialize()
    {
        User = await _localStorage.GetItemAsync<User>(_userKey);
    }

    public async Task<LoginResult> Login(LoginRequest model)
    {
        AuthCredentials authCredentials = new AuthCredentials()
        {
            Username = model.Email,
            Password = model.Password
        };
        try
        {

            var loginAsJson = JsonSerializer.Serialize(authCredentials);
            var response = await _httpService.PostAsync("api/auth/login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
            var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });


            if (loginResult.Successful == false)
                return null;

            await _localStorage.SetItemAsync("authToken", loginResult.Token);


            ((ApiAuthenticationStateProviderClient)_authenticationStateProvider).NotifyUserAuthentication(loginResult.Token);
            _httpService.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token);
            return loginResult;
        }
        catch (Exception ex)
        {
            string message = ex.Message;
            return null;
        }
       
    }

    public async Task Logout()
    {


        await _localStorage.ClearAsync();
        ((ApiAuthenticationStateProviderClient)_authenticationStateProvider).MarkUserAsLoggedOut();
        _httpService.DefaultRequestHeaders.Clear();
        _navigationManager.NavigateTo("/");

    
    }
}

}

任何帮助将不胜感激。如果我只允许其中一种,我知道这两种身份验证方法都是 100% 有效的。只是我一生都无法获得它,因此用户可以根据他们单击的登录按钮进行身份验证

【问题讨论】:

    标签: c# authentication azure-active-directory blazor blazor-webassembly


    【解决方案1】:

    这是 blazor 中已知的 bug

    您上面提到的错误意味着您的事件处理程序有问题。

    是的,微软发布了新版本,你可以更新你的应用。

    对于解决方法,您可以使用await Task.Yield()

    例如,这就是更新代码的样子:

    private a sync Task Callback(KeyboardEventArgs args)
    {
        if (args.Key == "Enter")
        {
            await Task.Yield();
            showInput = false;
        }
    }
    

    【讨论】:

    • 谢谢,我已经使用 Auth0,因为我认为没有解决办法
    猜你喜欢
    • 2023-01-22
    • 2020-10-29
    • 1970-01-01
    • 2018-01-26
    • 2020-10-13
    • 2023-01-30
    • 1970-01-01
    • 2022-10-22
    • 1970-01-01
    相关资源
    最近更新 更多