在Blazor服务器端使用SignInManager.SignInAsync(...)时,会抛出异常。

7

我正在学习Server side blazor并尝试学习认证。每当我使用SignInManager.SignInAsync(.......)时,它会抛出以下异常:

System.InvalidOperationException: 无法修改响应标头,因为响应已经开始。 at Microsoft.AspNetCore.HttpSys.Internal.HeaderCollection.ThrowIfReadOnly() at Microsoft.AspNetCore.HttpSys.Internal.HeaderCollection.set_Item(String key, StringValues value) at Microsoft.AspNetCore.Http.ResponseCookies.Append(String key, String value, CookieOptions options) at Microsoft.AspNetCore.Authentication.Cookies.ChunkingCookieManager.AppendResponseCookie(HttpContext context, String key, String value, CookieOptions options) at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties) at Microsoft.AspNetCore.Identity.SignInManager1.SignInWithClaimsAsync(TUser user, AuthenticationProperties authenticationProperties, IEnumerable1 additionalClaims) at GroupMembersInfo.Pages.RegisterUser.Register() in C:\my_work\Blazor_learning\GroupMembersInfo\Pages\RegisterUser.razor:line 52 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)

我认为异常是从我已经突出显示的方法中抛出的。那么我该如何解决这个问题。这里是我的代码

starup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using GroupMembersInfo.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;

namespace GroupMembersInfo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContextPool<AppDbContext>(
                options => options.UseSqlServer(Configuration.GetConnectionString("GMIDbConnection")));

            services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<AppDbContext>();

            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
            services.AddSingleton<WeatherForecastService>();

            services.ConfigureApplicationCookie(options =>
            {
                options.SlidingExpiration = true;

                options.Events.OnRedirectToLogin = cxt =>
                {
                    cxt.Response.StatusCode = 401;
                    return Task.CompletedTask;
                };

                options.Events.OnRedirectToAccessDenied = cxt =>
                {
                    cxt.Response.StatusCode = 403;
                    return Task.CompletedTask;
                };

                options.Events.OnRedirectToLogout = cxt => Task.CompletedTask;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

登录组件代码

@page "/loginPage"

@using Microsoft.AspNetCore.Identity;
@using GroupMembersInfo.Data;

@inject UserManager<IdentityUser> UserManager
@inject SignInManager<IdentityUser> SignInManager
@inject NavigationManager NavigationManager

<h3>Log In</h3>

<div class="row">
    <div class="col-md-12">
        <EditForm Model="@RegisterUserModel" OnValidSubmit="@LogIn">
            <div class="form-group">
                <label>Iser Id: </label>
                <input @bind-value="RegisterUserModel.Email" class="form-control" />
            </div>
            <div class="form-group">
                <label>Password: </label>
                <input @bind-value="RegisterUserModel.Password" class="form-control" />
            </div>
            <div class="form-group">
                <button type="submit">Submit</button>
            </div>
        </EditForm>
    </div>
</div>

@code {
    public RegisterUserModel RegisterUserModel { get; set; } = new RegisterUserModel();

    public async Task LogIn()
    {
        var user = new IdentityUser
        {
            UserName = RegisterUserModel.Email
        };

        user.PasswordHash = SignInManager.UserManager.PasswordHasher.HashPassword(user, RegisterUserModel.Password);

        await SignInManager.SignInAsync(user, isPersistent: false);

        NavigationManager.NavigateTo("/");
    }
}
3个回答

3
我按照您的建议修改了代码,并且它成功运行。
        await SignInManager.SignInAsync(user, isPersistent: false).ContinueWith(prop =>
        {
            NavigationManager.NavigateTo("/");
        });

这段代码可能不会抛出异常,但它是否设置了认证 cookie?也就是说,它是否将用户显示为已登录? - David Masters

2
问题出在以下两行代码中:
await SignInManager.SignInAsync(user, isPersistent: false);

NavigationManager.NavigateTo("/");

在SignInAsync完成之前,页面正在导航到下一页。这就是为什么SignInAsync无法完成其工作的原因。

解决方案:

如果在NavigationManager.NavigateTo之前添加return语句,它应该可以解决此问题。

这是因为该方法在等待的操作完成之前不能返回。

return NavigationManager.NavigateTo("/");

希望这能帮到您。

非常感谢您的建议。我按照下面的方式修改了我的代码,现在它可以正常工作了。await SignInManager.SignInAsync(user, isPersistent: false).ContinueWith(prop => { NavigationManager.NavigateTo("/"); }); - Sanjay Jain

1
给出的答案对我没有用,也许在当前的Blazor版本中情况已经不同了。帮助我解决问题的是MarcoTheFirst提供的以下解决方案:https://github.com/dotnet/aspnetcore/issues/13601

创建一个Login.razor组件并注入SignInManager和NavigationManager。使用SignInManager使用CheckPasswordSignInAsync()方法验证密码。不要调用PasswordSignInAsync(),因为它会抛出前面提到的异常。相反,将凭据传递给自定义中间件中的凭据缓存(参见下一段)。然后调用NavigationManager.NavigateTo(/login?key=, true)执行完整的postback,这是设置cookie所必需的。

创建一个中间件类(我称之为BlazorCookieLoginMiddleware):在其中使用静态字典缓存来自Blazor登录组件的登录信息。同时,拦截到“/login?key=”的请求,然后使用SignInManager执行实际的登录操作。这是因为中间件在管道中较早地执行,此时仍然可以设置cookie。凭据可以从静态字典缓存中检索,并应立即从字典中删除。如果认证成功,则将用户重定向到应用程序根目录“/”或任何您想要的位置。
请参阅代码示例链接。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接