Blazor 5x - قسمت 33 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 3- بهبود تجربهی کاربری عدم دسترسیها
نویسنده: وحید نصیری
تاریخ: ۱۴۰۰/۰۱/۱۱ ۱۷:۵۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
@using BlazorWasm.Client.Pages.Authentication
@using System.Security.Claims
@inject NavigationManager NavigationManager
if(AuthState is not null)
{
<div class="alert alert-danger">
<p>You [@AuthState.User.Identity.Name] do not have access to the requested page</p>
<div>
Your roles:
<ul>
@foreach (var claim in AuthState.User.Claims.Where(c => c.Type == ClaimTypes.Role))
{
<li>@claim.Value</li>
}
</ul>
</div>
</div>
}
@code
{
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState {set; get;}
AuthenticationState AuthState;
protected override async Task OnInitializedAsync()
{
AuthState = await AuthenticationState;
if (!IsAuthenticated(AuthState))
{
var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
if (string.IsNullOrEmpty(returnUrl))
{
NavigationManager.NavigateTo("login");
}
else
{
NavigationManager.NavigateTo($"login?returnUrl={Uri.EscapeDataString(returnUrl)}");
}
}
}
private bool IsAuthenticated(AuthenticationState authState) =>
authState?.User?.Identity is not null && authState.User.Identity.IsAuthenticated;
} در ادامه برای استفاده از این کامپوننت، به کامپوننت ریشهای BlazorWasm.Client\App.razor مراجعه کرده و قسمت NotAuthorized آنرا به صورت زیر، با معرفی کامپوننت RedirectToLogin، جایگزین میکنیم:
<NotAuthorized>
<RedirectToLogin></RedirectToLogin>
</NotAuthorized> using System;
using BlazorServer.Common;
using Microsoft.AspNetCore.Authorization;
namespace BlazorWasm.Client.Utils
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RolesAttribute : AuthorizeAttribute
{
public RolesAttribute(params string[] roles)
{
Roles = $"{ConstantRoles.Admin},{string.Join(",", roles)}";
}
}
} @attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
namespace BlazorWasm.WebApi.Controllers
{
[Route("api/[controller]")]
[Authorize(Roles = "Editor")]
public class MyProtectedEditorsApiController : Controller
{
[HttpGet]
public IActionResult Get()
{
return Ok(new ProtectedEditorsApiDTO
{
Id = 1,
Title = "Hello from My Protected Editors Controller!",
Username = this.User.Identity.Name
});
}
}
} namespace BlazorWasm.Client.Services
{
public class ClientHttpInterceptorService : DelegatingHandler
{
private readonly NavigationManager _navigationManager;
private readonly ILocalStorageService _localStorage;
private readonly IJSRuntime _jsRuntime;
public ClientHttpInterceptorService(
NavigationManager navigationManager,
ILocalStorageService localStorage,
IJSRuntime JsRuntime)
{
_navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager));
_localStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage));
_jsRuntime = JsRuntime ?? throw new ArgumentNullException(nameof(JsRuntime));
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// How to add a JWT to all of the requests
var token = await _localStorage.GetItemAsync<string>(ConstantKeys.LocalToken);
if (token is not null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
await _jsRuntime.ToastrError($"Failed to call `{request.RequestUri}`. StatusCode: {response.StatusCode}.");
switch (response.StatusCode)
{
case HttpStatusCode.NotFound:
_navigationManager.NavigateTo("/404");
break;
case HttpStatusCode.Forbidden: // 403
case HttpStatusCode.Unauthorized: // 401
_navigationManager.NavigateTo("/unauthorized");
break;
default:
_navigationManager.NavigateTo("/500");
break;
}
}
return response;
}
}
} @page "/unauthorized"
<div class="alert alert-danger mt-3">
<p>You don't have access to the requested resource.</p>
</div> dotnet add package Microsoft.Extensions.Http
namespace BlazorWasm.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
//...
// builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
/*builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl"))
});*/
// dotnet add package Microsoft.Extensions.Http
builder.Services.AddHttpClient(
name: "ServerAPI",
configureClient: client =>
{
client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl"));
client.DefaultRequestHeaders.Add("User-Agent", "BlazorWasm.Client 1.0");
}
)
.AddHttpMessageHandler<ClientHttpInterceptorService>();
builder.Services.AddScoped<ClientHttpInterceptorService>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));
//...
}
}
} using System.Security.Claims;
using Microsoft.AspNetCore.Authorization; // dotnet add package Microsoft.AspNetCore.Authorization
namespace BlazorServer.Common
{
public static class PolicyTypes
{
public const string RequireAdmin = nameof(RequireAdmin);
public const string RequireCustomer = nameof(RequireCustomer);
public const string RequireEmployee = nameof(RequireEmployee);
public const string RequireEmployeeOrCustomer = nameof(RequireEmployeeOrCustomer);
public static AuthorizationOptions AddAppPolicies(this AuthorizationOptions options)
{
options.AddPolicy(RequireAdmin, policy => policy.RequireRole(ConstantRoles.Admin));
options.AddPolicy(RequireCustomer, policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
&& (claim.Value == ConstantRoles.Admin || claim.Value == ConstantRoles.Customer))
));
options.AddPolicy(RequireEmployee, policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
&& (claim.Value == ConstantRoles.Admin || claim.Value == ConstantRoles.Employee))
));
options.AddPolicy(RequireEmployeeOrCustomer, policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
&& (claim.Value == ConstantRoles.Admin ||
claim.Value == ConstantRoles.Employee ||
claim.Value == ConstantRoles.Customer))
));
return options;
}
}
} namespace BlazorWasm.WebApi
{
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthorization(options => options.AddAppPolicies());
// ... namespace BlazorWasm.Client
{
public class Program
{
public static async Task Main(string[] args)
{
// ...
builder.Services.AddAuthorizationCore(options => options.AddAppPolicies());
// ...
}
}
} @page "/hotel-room-details/{Id:int}"
// ...
@*
@attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
*@
@attribute [Authorize(Policy = PolicyTypes.RequireEmployeeOrCustomer)] <AuthorizeView Policy="@PolicyTypes.RequireEmployeeOrCustomer">
<p>You can only see this if you're an admin or an employee or a customer.</p>
</AuthorizeView> public class UserCanSeeProjectRequirement : IAuthorizationRequirement
{
public UserCanSeeProjectRequirement() { }
}
public class UserCanSeeProjectHandler : AuthorizationHandler<UserCanSeeProjectRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
UserCanSeeProjectRequirement requirement)
{
//claim-based validation
if (context.User.HasClaim("permission.cansee", "CanSee"))
context.Succeed(requirement);
//role-based validation
if (context.User.IsInRole("admin") || context.User.IsInRole("user"))
context.Succeed(requirement);
return Task.CompletedTask;
}
} namespace BlazorWasm.Client
{
public class Program
{
public static async Task Main(string[] args)
{
// ...
services.AddScoped<IAuthorizationHandler, UserCanSeeProjectHandler>();
services.AddAuthorizationCore(options => {
options.AddPolicy("UserCanSeeProjectPolicy", policy => policy.Requirements.Add(new UserCanSeeProjectRequirement()));
});
// ...
}
}
} @attribute [Authorize(Policy = "UserCanSeeProjectPolicy")]
<AuthorizeView Policy="UserCanSeeProjectPolicy">
<NotAuthorized>
<h2 class="mt-5">You are not authorized to view this page</h2>
</NotAuthorized>
<Authorized>
<div class="container my-profile">
--- Place here all the content you want your user to view ----
</div>
</Authorized>
</AuthorizeView>