Несколько вопросов про blazor server side
Начал изучать blazor (SERVER SIDE(!)) и столкнулся с не понимаем и невозможностью найти ответы на свои вопросы в гугле.
- Как сделать так, чтобы при заходе на сайт сразу перенаправляло на страницу авторизации, т.е. все страницы сайты были доступны только авторизованному пользователю;
- Можно ли перенести страницы в библиотеки? Попытался перенести страницу авторизации в отдельную библиотеку в папку Pages, но приложение упорно не видит эту страницу. Выдает только "Sorry, there's nothing at this address.";
Возможно, частичный ответ на первый вопрос у меня есть из гугла, но я не уверен, что он правильный, прикладываю:
Добавил атрибут авторизации в _Imports.razor:
@attribute [Authorize]
В App.razor поменял структуру на :
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin/>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
где RedirectToLogin - компонент razor, лежащий отдельно в другой библиотеке (он работает и всё прекрасно перенаправляет, но страницу /login он не видит):
@inject NavigationManager navManager
@code {
protected override void OnInitialized()
{
navManager.NavigateTo("login");
}
}
Где страница /login (находится в другой библиотеке по пути: MySite.SecondLib/Pages/Login.razor):
@page "/login"
<h1>LOGIN PAGE</h1>
Ответы (1 шт):
Я использую blazor-wasm, но думаю и на server-side все нижесказанное будет работать.
Ответ на 1й вопрос.
- Удалите @attribute [Authorize] из imports
- В компоненте RedirectToLogin вызывайте navManager.NavigateTo("login"); не из OnInitialized, а в OnAfterRender. На ServerSide не тестил это, но на WASM словите null.
- Вам нужно объявить свою реализацию класса AuthenticationStateProvider и переопределить его метод GetAuthenticationStateAsync, который каждый раз будет вызываться для проверки аутентификации пользователя. Также, эту реализацию нужно внедрить в наш контейнер.
services.AddScoped<AuthenticationStateProvider, YourStateProvider>();
Короткая версия моего переопределения:
public class YourStateProvider : AuthenticationStateProvider
{
//Мой сервис для идентификации, аутентификации, авторизации
private readonly ISecurityService _securityService;
public YourStateProvider(ISecurityService securityService)
{
_securityService = securityService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
//Проверяю, если уже в памяти содержится JWT
if (!string.IsNullOrWhiteSpace(_securityService.AccessToken))
{
return GetAuthState();
}
try
{
//Иначе пытаюсь обновить его. New - вызов cqrs-команды
await _securityService.RefreshTokensAsync(new());
return GetAuthState();
}
catch
{
//Возвращаю анонимного пользователя
return GetAnonymousState();
}
}
#region Private methods
private AuthenticationState GetAnonymousState()
{
var anonymousIdentity = new ClaimsIdentity();
var anonymousPrincipal = new ClaimsPrincipal(anonymousIdentity);
return new AuthenticationState(anonymousPrincipal);
}
private AuthenticationState GetAuthState()
{
var identity = new ClaimsIdentity(_securityService.Claims, "jwtAuthType");
var principal = new ClaimsPrincipal(identity);
return new AuthenticationState(principal);
}
#endregion
}
Но в моем реальном коде более сложная логика. Вам, скорее всего, можно создать еще один публичный метод, который вы будете вызывать после успешной авторизации, например, из той же страницы входа. Примерный листинг:
public class LoginViewModel : ViewModelBase
{
private readonly ISecurityService _securityService;
private readonly YourStateProvider _stateProvider;
private readonly NavigationManager _navigationManager;
public LoginViewModel(
ISecurityService securityService,
AuthenticationStateProvider authStateProviderб
NavigationManager navigationManager)
{
_securityService = securityService;
_stateProvider = (YourStateProvider)authStateProvider;
_navigationManager = navigationManager;
}
private async Task LoginExecuteAsync()
{
await _securityService.LoginAsync("Отправляем запрос на авторизацию login/pass");
_stateProvider.NotifyStateChanged();//Уведомляем наш StateProvider о том, что вы вошли
_navigationManager.NavigateTo("/"); //Редирект в корень
}
}
Полезные ссылки:
А страницы в библиотеки не переносил. В отдельной библиотеке у меня лежат сервисы, ресурсы, api и прочее-прочее. В клиенте(wasm) это все подключаю, разместил пару компонентов и 90% файлов - view, view.cs + viewmodel