【问题标题】:How to inject a ClaimsPrincipal in a Blazor Server application如何在 Blazor 服务器应用程序中注入 ClaimsPrincipal
【发布时间】:2021-10-28 04:42:24
【问题描述】:

以下是一些有助于理解问题的工件:

  • 示例代码 - Github repo
  • 已部署的应用程序 - 不再可用

更新:我关注了这个 YouTube 视频,我现在认为它是正确在 Blazor 服务器应用程序的依赖服务中访问有关经过身份验证的用户信息的方法: https://www.youtube.com/watch?v=Eh4xPgP5PsM.

我已更新 Github 代码以反映该解决方案。


我在我的 ASP.NET MVC Core 应用程序中使用依赖注入注册了以下类。

public class UserContext
{
    ClaimsPrincipal _principal;

    public UserContext(ClaimsPrincipal principal) => _principal = principal;

    public bool IsAuthenticated => _principal.Identity.IsAuthenticated;
}


public class WrapperService
{
   UserContext _userContext; 

   public WrapperService(UserContext context) => _userContext = context;

   public bool UserHasSpecialAccess() 
   {
        return _userContext.IsAuthenticated;
   }
}

IoC 依赖注册在 Startup.cs 中配置

services.AddScoped<ClaimsPrincipal>(x =>
{
    var context = x.GetRequiredService<IHttpContextAccessor>();
    return context.HttpContext.User;
});

services.AddScoped<UserContext>();
services.AddScoped<WrapperService>();

我最近在 MVC 应用程序中启用了 Blazor,并希望在我的 Blazor 组件中使用我的 DI 注册服务。

我将服务注入到 Blazor 组件中以便像这样使用它:

@inject WrapperService _Wrapper

但是,当我尝试从服务器端处理程序使用服务时,请求失败并出现异常,抱怨无法构造服务 - 由于 IHttpContext 在对服务器的后续调用中不存在。

<button @onclick="HandleClick">Check Access</button>

async Task HandleClick()
{
    var hasPermission = _Wrapper.UserHasSpecialAccess(); // fails ????
}

我想我理解为什么在 Blazor Server 应用程序中使用 IHttpContextAccessor 不起作用/不推荐使用。我的问题是,如果没有它,我如何访问我的服务中需要的声明?

对我来说奇怪的是,当我在开发环境中的 IIS Express 下运行它时,这一切正常,但当我部署并尝试从 Azure AppService 中运行它时却失败了。

【问题讨论】:

  • 您正在使用服务器端 Blazor,在这种情况下您可以访问 httpcontext。您的服务应该使用 HttpContext,然后从上下文中访问 Claims。 UserContext(IHttpContextAccessor ...) 你的 github 链接也显示 404。
  • 如果某些页面没有用户登录,我会创建一个临时用户并使用中间件登录。您可能有兴趣查看我的 Invoke 方法的信号:public async Task Invoke (HttpContext context, UserManager&lt;IdentityUser&gt; _userManager, SignInManager&lt;IdentityUser&gt; _signInManager) 所有这些东西都会自动注入到方法中。
  • 感谢@AliK,repo 是私有的。我已经更改了可见性,现在它是公开的????
  • 嗨@Bennyboy1973,根据这篇文章,Razor 组件不支持 SignInManager 和 UserManager。 docs.microsoft.com/en-us/aspnet/core/blazor/security/… 我还注意到 HttpContext(通过 IHttpContextAccessor)并不总是可用 - 因此我的 Azure 托管示例存在问题
  • 你有没有设法解决这个问题?我也有一个与您的想法类似的服务项目,我在其中注入 ClaimsPrincipal 以获取登录用户,但在 blazor 中找不到我的方式。我不想注入 authenticationStateProvider 因为服务项目应该独立于 UI。

标签: c# blazor blazor-server-side


【解决方案1】:

您可以将 AuthenticationStateProvider 注入到您的 Service 构造函数中,然后使用

var principal = await _authenticationStateProvider.GetAuthenticationStateAsync();

AuthenticationStateProvider 是一个 Scoped 服务,因此您的服务也必须如此。

【讨论】:

  • 谢谢@henk,我会看看这个。我想我确实尝试过使用AuthenticationStateProvider,但我一直收到错误消息,说在 SetAuthenticationState 之前调用了 GetAuthenticationStateAsync。我会再试一次,并确保我的范围已正确注册!
  • 感谢@Henk,我得到了这个工作 - 请参阅我提供的演示应用程序中的示例 #3。将 AuthenticationStateProvider 作为对其他服务的依赖项传递,感觉有点脏。根据文档,这不是一种受欢迎的方法。
【解决方案2】:

使用 CascadingAuthenticationState 访问声明主体

https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0#expose-the-authentication-state-as-a-cascading-parameter-1

如果您需要使用自己的逻辑,则需要实现自己的身份验证状态提供程序。

如果您想使用服务来使用 ClaimsPrincipal,您可以执行以下操作:

ClaimsPrincipalUserService.cs


ClaimsPrincipal claimsPrincipal;
void SetClaimsPrincipal(ClaimsPrincipal cp)
{
   claimsPrincipal = cp;
  // any logic + notifications which need to be raised when 
  // ClaimsPrincipal has changes
}

在启动时注入此服务。

在布局中

MainLayout.razor

@inject ClaimsPrincipalUserService cpus;

[CascadingParameter]
public Task<AuthenticationState> State {get;set;}

protected override async Task OnInitializedAsync()
{
   var state = await State;
   var user = state.User; // Get claims principal.
   cpus.SetClaimsPrincipal(user);
}

【讨论】:

  • CascadingAuthenticationState 将声明主体本地化到剃刀页面。但我看不出这对服务中的依赖注入有何帮助?
  • 将 Claims Principal 传递给您要使用的服务。这是使用它的一种方式。
  • 感谢@mayur-ekbote,这就是我的想法……但我不确定如何构造一个声明主体以放入 DI 容器中——尤其是无法访问 HttpContext .有什么例子可以指点我吗?
  • 使用示例代码 sn-p 更新。
  • 看,如果您尝试在为 Y 设计的范例中实现 X,您将不会获得干净的架构。Blazor 服务器被设计为有状态的前端架构。您正在尝试使用与典型的 REST API/MVC over http 相关的模式。显然,会有办法做到这一点,但它可能看起来很老套。你介意检查一下你是否遇到了 XY 问题吗?
【解决方案3】:

这对我有用,为 AuthenticationStateProvider 编写派生类。

public class AppAuthenticationStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal principal;

    // Constructor, only needed when injections required
    public AppAuthenticationStateProvider(/* INJECTIONS HERE */)
        : base()
    {
        principal ??= new();
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        return Task.FromResult(new AuthenticationState(principal));
    }

    // Method called from login form view
    public async Task LogIn(/* USER AND PASSWORD */)
    {
        // Create session
        principal = new ClaimsPrincipal(...);
        var task = Task.FromResult(new AuthenticationState(principal));
        NotifyAuthenticationStateChanged(task);
    }

    // Method called from logout form view
    public async Task LogOut()
    {
        // Close session
        principal = new();
        var task = Task.FromResult(new AuthenticationState(principal));
        NotifyAuthenticationStateChanged(task);
    }

然后,在程序/启动时添加以下行:

// Example for .Net 6
builder.Services.AddScoped<AuthenticationStateProvider, AppAuthenticationStateProvider>();
builder.Services.AddScoped<ClaimsPrincipal>(s =>
{
    var stateprovider = s.GetRequiredService<AuthenticationStateProvider>();
    var state = stateprovider.GetAuthenticationStateAsync().Result;
    return state.User;
});

就是这样。现在,您可以在任何地方注入 ClaimsPrincipal。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    相关资源
    最近更新 更多