Вход в систему .NET Blazor без каркаса удостоверений

Я пытаюсь добавить аутентификацию / вход в свое приложение Blazor-server. Я не хочу использовать материал Identity, который настраивает страницы и базу данных.

Когда я сделал это с помощью бритвенных страниц, у меня могла быть страница для входа в систему с таким кодом:

    var claims = new List<Claim>{
        new Claim(ClaimTypes.Name, Input.Name),
    };

    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var principal = new ClaimsPrincipal(identity);
    await HttpContext.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme, principal);

Это будет входить в систему с использованием файлов cookie, и я могу заблокировать доступ к страницам или показать / скрыть материал на основе утверждений или независимо от того, вошли вы в систему или нет.

Теперь, если бы я использовал Identity, он бы настроил бритвенные страницы для входа в систему и управления пользователями, но вместо этого я пытаюсь выполнить функцию входа в систему с помощью компонента razor, и я действительно не могу найти подход к этому. Я могу попробовать ввести HttpContextAccessor и использовать это:

    HttpContext httpContext = HttpContextAccessor.HttpContext;
    await httpContext.SignInAsync(
        CookieAuthenticationDefaults.AuthenticationScheme, principal);

Но это вызывает исключение: Компонент рендеринга необработанного исключения: заголовки доступны только для чтения, ответ уже начался. System.InvalidOperationException: заголовки доступны только для чтения, ответ уже начался.

Похоже, что большая часть того, что я могу найти, основывается на подходе к страницам бритвы.

Возможно ли войти в систему с помощью бритвенного компонента?


person Troels    schedule 19.11.2019    source источник


Ответы (2)


Невозможно получить HttpContext в компоненте Blazor, см. Этот поток: HttpContext всегда имеет значение null

Если вы хотите получить доступ к заявкам на стороне клиента, вам следует использовать OAuth2 или OIDC. Вы можете использовать Authfix / Blazor-Oidc или sotsera / sotsera.blazor.oidc, например

Другой подход - зарегистрировать пользователя с помощью классической страницы Identity razor, но не на вашей странице blazor. Затем получите доступ к заявкам пользователей на стороне сервера в вашем веб-API. Но это означает, что все развертывается на одном хосте.

person agua from mars    schedule 19.11.2019
comment
Вы можете получить HttpContext с помощью инъекции Add services.AddScoped ‹HttpClient› (); к вашим услугам. Теперь вы можете получить HttpClient, просто введя [Inject] HttpClient _httpClient {get; установленный; } Я тоже использовал это в своем проекте. Возможно, вам придется добавить System.Net.Http; за nuget. Я не уверен в этом шаге. Это было слишком давно. - person Taladan; 21.04.2020
comment
Но вы имеете в виду Blazor на стороне клиента? Я сомневаюсь, что вы сможете сделать это на стороне сервера Blazor. Установка cookie в коде, который выполняется на сервере, кажется почти невозможным. - person Heinzlmaen; 24.08.2020

Это может быть сделано. Вот принцип:

  1. Создайте компонент Login.razor и вставьте SignInManager и NavigationManager. Используйте SignInManager для проверки пароля с помощью метода CheckPasswordSignInAsync (). НЕ вызывайте PasswordSignInAsync (), поскольку он вызовет исключение, упомянутое ранее. Вместо этого передайте учетные данные в кэш учетных данных в настраиваемом промежуточном программном обеспечении (см. Следующий абзац). Затем вызовите NavigationManager.NagigateTo (/ login? Key =, true), чтобы выполнить полную обратную передачу, которая требуется для установки файла cookie.

  2. Создайте класс промежуточного программного обеспечения (я назвал его BlazorCookieLoginMiddleware): в нем вы используете статический словарь для кэширования информации для входа из компонента входа в Blazor. Кроме того, вы перехватываете запрос к / login? Key =, а затем выполняете фактический вход с помощью SignInManager. Это работает, потому что промежуточное программное обеспечение выполняется раньше в конвейере, когда файлы cookie еще могут быть установлены. Учетные данные могут быть получены из кеша статического словаря и должны быть немедленно удалены из dict. Если аутентификация прошла успешно, вы просто перенаправляете пользователя в корень приложения / или куда хотите.

Я проверил это, он работает как шарм. Я также успешно добавил 2FA, но это было бы слишком для этой публикации.

Вот некоторый код (обратите внимание: крайние случаи и ошибки не обрабатываются правильно для простоты; просто PoC):

Login.razor:

@page "/login"
@attribute [AllowAnonymous]
@inject SignInManager<ApplicationUser> SignInMgr
@inject UserManager<ApplicationUser> UserMgr
@inject NavigationManager NavMgr

<h3>Login</h3>

    <label for="email">Email:</label>
    <input type="email" @bind="Email" name="email" />
    <label for="password">Password:</label>
    <input type="password" @bind="password" name="password" />
    @if (!string.IsNullOrEmpty(error))
    {
        <div class="alert-danger">
            <p>@error</p>
        </div>
    }
    <button @onclick="LoginClicked">Login</button>

@code {
    public string Email { get; set; }

    private string password;
    private string error;

    private async Task LoginClicked()
    {
        error = null;
        var usr = await UserMgr.FindByEmailAsync(Email);
        if (usr == null)
        {
            error = "User not found";
            return;
        }


        if (await SignInMgr.CanSignInAsync(usr))
        {
            var result = await SignInMgr.CheckPasswordSignInAsync(usr, password, true);
            if (result == Microsoft.AspNetCore.Identity.SignInResult.Success)
            {
                Guid key = Guid.NewGuid();
                BlazorCookieLoginMiddleware.Logins[key] = new LoginInfo { Email = Email, Password = password };
                NavMgr.NavigateTo($"/login?key={key}", true);
            }
            else
            {
                error = "Login failed. Check your password.";
            }
        }
        else
        {
            error = "Your account is blocked";
        }
    }
}

BlazorCookieLoginMiddleware.cs:

    public class LoginInfo
    {
        public string Email { get; set; }
        public string Password { get; set; }
    }

    public class BlazorCookieLoginMiddleware
    {
        public static IDictionary<Guid, LoginInfo> Logins { get; private set; }
            = new ConcurrentDictionary<Guid, LoginInfo>();        


        private readonly RequestDelegate _next;

        public BlazorCookieLoginMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context, SignInManager<ApplicationUser> signInMgr)
        {
            if (context.Request.Path == "/login" && context.Request.Query.ContainsKey("key"))
            {
                var key = Guid.Parse(context.Request.Query["key"]);
                var info = Logins[key];

                var result = await signInMgr.PasswordSignInAsync(info.Email, info.Password, false, lockoutOnFailure: true);
                info.Password = null;
                if (result.Succeeded)
                {
                    Logins.Remove(key);
                    context.Response.Redirect("/");
                    return;
                }
                else if (result.RequiresTwoFactor)
                {
                    //TODO: redirect to 2FA razor component
                    context.Response.Redirect("/loginwith2fa/" + key);
                    return;
                }
                else
                {
                    //TODO: Proper error handling
                    context.Response.Redirect("/loginfailed");
                    return;
                }    
            }     
            else
            {
                await _next.Invoke(context);
            }
        }
    }

и не забудьте добавить новое промежуточное ПО в Startup.cs:

        public void Configure(IApplicationBuilder app)
        {
            //.....
            app.UseAuthentication();
            app.UseAuthorization();
            
            app.UseMiddleware<BlazorCookieLoginMiddleware>();
            //.....
        }
person Marco    schedule 25.08.2020