【问题标题】:Get Current User in a Blazor component在 Blazor 组件中获取当前用户
【发布时间】:2020-06-01 12:22:39
【问题描述】:

我正在使用 Blazor 和 Windows 身份验证启动一个新站点,并且需要识别查看页面/组件的当前用户。

对于 Razor 页面,可以使用 Context.User.Identity.Name 访问当前用户名,但这似乎不适用于 Blazor 组件。我尝试将 HttpContext 注入组件,但 Context 在运行时为空。

作为奖励,我最终希望将其合并到 Startup.cs 中,因此我只需要获取一次用户名,并且可以为我的应用程序利用企业用户类(使用 EF Core)。也将不胜感激针对该用例量身定制的答案。

【问题讨论】:

  • 以下链接是一个答案,您可以在其中了解如何在初始调用时从 Blazor 应用程序访问 HttpContext,该调用始终是 HTTP,而不是 WebSocket 连接 (SignalR) stackoverflow.com/a/59538319/6152891
  • 以下链接是一个答案,您可以在其中了解如何在初始调用时从 Blazor 应用程序访问 HttpContext,该调用始终是 HTTP,而不是 WebSocket 连接 (SignalR) stackoverflow.com/a/59538319/6152891

标签: asp.net-core blazor .net-core-3.0 blazor-server-side ef-core-3.0


【解决方案1】:

在组件中获取用户有三种可能(页面就是组件):

  1. 注入IHttpContextAccessor并从中访问HttpContext然后User;需要在Startup.ConfigureServices注册IHttpContextAccessor,一般使用AddHttpContextAccessor。编辑:根据Microsoft docs you must not do this for security reasons
  2. 注入AuthenticationStateProvider 属性,调用GetAuthenticationStateAsync 并从中获取User
  3. 将您的组件包装在<CascadingAuthenticationState> 组件中,声明Task<AuthenticationState> 属性并调用它以获取User(类似于#2)

在此处查看更多信息:https://docs.microsoft.com/en-us/aspnet/core/security/blazor

【讨论】:

  • "注入 IHttpContextAccessor 并从中访问 HttpContext 和 User" 。这不是真的。 WebSocket 连接与 HTTP 请求有什么关系
  • 选项 2 对我有用,并且很容易用于在页面中显示用户名。 @authStateProvider.GetAuthenticationStateAsync().Result.User.Identity.Name
  • 选项 1 在开发过程中对我有用,但在 IIS 中发布后不起作用。 ??所以我结束了使用选项 2
  • 1,IHttpContextAccessor -> 不,不,不,永远! :) 无需进行“专业”解释,实际上它会返回服务器的 HttpContext 并在用户之间共享!实际上它导致了我现在遇到的一些问题:a,用户信息,角色混淆,b,通过更改主题将应用于所有其他用户!所以我今天学到的是:永远不要使用 IHttpContextAccessor,永远不要做任何单例/静态的东西!非常仔细地检查注入的服务!!一些注册为services.AddXYZ的服务会自动注册为Singleton!选项 2 和 3 很好,很好的建议。
  • @IstvánPiroska - 这似乎不对。根据文档,HttpContext“封装了有关单个 HTTP 请求的所有 HTTP 特定信息”。如果身份已到位,这包括该请求的用户。如果我不正确,我想知道。你能指出一个“专业”的解释吗?我过去一直使用 HttpContext 来检索当前的主体声明。
【解决方案2】:

我现在已经能够让它与一个通用类以及一个组件一起工作。

访问HttpContext 用户;在ConfigureServices,在Startup.cs 添加

services.AddHttpContextAccessor();

我的CorporateUser 课程有一个CorporateUserService 课程。服务类通过构造函数注入获得DbContext

然后我创建了一个新的CurrentCorporateUserService,它继承自CorporateUserService。它通过构造函数注入接受DbContextIHttpContextAccessor

public class CurrentCorporateUserService : CorporateUserService
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CurrentCorporateUserService(IHttpContextAccessor httpContextAccessor,
        MyDbContext context) : base(context)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    ...

基础服务类有一个方法GetUserByUsername(string username)。 Current 服务类增加了一个额外的方法

public CorporateUser GetCurrentUser()
{
    return base.GetUserByUsername(_httpContextAccessor.HttpContext.User.Identity.Name.Substring(8));
}

当前服务类注册在Startup.cs

services.AddScoped<CurrentCorporateUserService>();

完成后,我可以在带有指令注入的组件中使用CurrentCorporateUserService

[Inject]
private CurrentCorporateUserService CurrentCorporateUserService { get; set; } = 
default!;

或在任何类中,使用构造函数注入。

public MyDbContext(DbContextOptions<MyDbContext> options,
    CurrentCorporateUserService CurrentCorporateUserService)
    : base(options) 
{
    _currentUser = CurrentCorporateUserService.GetCurrentUser();
}

使其成为项目范围的服务意味着我的所有开发人员都不必关心如何获取当前用户,他们只需将服务注入到他们的类中。

例如,在MyDbContext 中使用它使当前用户可用于每个保存事件。在下面的代码中,任何继承BaseReport 类的类都会在保存记录时自动更新报表元数据。

public override Int32 SaveChanges()
{         
    var entries = ChangeTracker.Entries().Where(e => e.Entity is BaseReport
    && (e.State == EntityState.Added || e.State == EntityState.Modified));

    foreach (var entityEntry in entries)
    {
        ((BaseReport)entityEntry.Entity).ModifiedDate = DateTime.Now;
        ((BaseReport)entityEntry.Entity).ModifiedByUser = _currentUser.Username;
    }

    return base.SaveChanges();
}

【讨论】:

  • 这似乎是对的,但我正在为我的组件使用基类。我是否也需要基类中的指令注入?我正在尝试这样做并在基本构造函数中调用 userservice 上的方法,但 useservice 为 null
  • 忽略,不能在基类构造函数中使用。它当时不可用。
  • @Wes H 部署到生产服务器环境时不起作用。您能否提供替代解决方案以实现相同的效果...?
  • 提问者提供的这个答案是错误的。在提供任何答案之前,我已经对这个问题发表了评论。但是这里的人,包括提问者,选择忽略它。请阅读上面 Balagurunathan Marimuthu 的评论...
  • 如上面的答案所述,不要这样做。请参阅docs.microsoft.com/en-us/aspnet/core/fundamentals/… 了解更多信息。
【解决方案3】:

对我来说,第一个答案 2. 中提到的解决方案非常完美: 我在 .Net Core 5.0 上使用 Blazor 服务器端。 我注射了

@inject AuthenticationStateProvider GetAuthenticationStateAsync

在我的 Blazor 页面中并在代码部分添加以下内容:

protected async override Task OnInitializedAsync()
    {
        var authstate = await GetAuthenticationStateAsync.GetAuthenticationStateAsync();
        var user = authstate.User;
        var name = user.Identity.Name;
    }

在我的 startup.cs 中,我有以下几行:

services.AddScoped<ApiAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(p =>
                p.GetRequiredService<ApiAuthenticationStateProvider>());

【讨论】:

  • 我认为在 .NET 5.0 中添加到 Startup 是不必要的。
  • 你也可以省略var name = user.Identity.Name;而只写var user = authstate.User.Identity.Name;
  • 这不适用于 Blazor WASM + .Net 5.0。当我登录时,authState.User.Identity.Name 始终为空。但是当我刷新时,.Name 就可以了。
【解决方案4】:

对于blazor wasm 中的net 5.0 及更高版本。这是我的做法,

  1. 如下所示,将&lt;App&gt; 组件包裹在&lt;CascadingAuthenticationState&gt; 中,
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            ...
        </Found>
        <NotFound>
            ...
        </NotFound>
    </Router>
</CascadingAuthenticationState>
  1. 然后如下图在任意组件内添加Task&lt;AuthenticationState&gt;CascadingParameter
public class AppRootBase : ComponentBase
{
    [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
}
  1. 现在您可以在组件内部访问登录用户IdentityClaims,如下所示,
protected override async Task OnInitializedAsync()
{
    var authState = await authenticationStateTask;
    var user = authState.User;

    if (user.Identity.IsAuthenticated)
    {
        Console.WriteLine($"{user.Identity.Name} is authenticated.");
    }
}

这是来自Microsoft docs的参考。

【讨论】:

  • 为了让它工作,我不得不将OnInitializedAsync()更改为OnParametersSetAsync(),因为user.Identity.Name在成功登录后总是为空。
【解决方案5】:

我也有类似需求,一直在使用:

var authstate = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;

我的 startup.cs 中已经有一个AuthenticationStateProvider,并将其添加到我的自定义类的构造函数中。

【讨论】:

    【解决方案6】:

    如果您创建一个新项目并选择带有 Windows 身份验证的 Blazor,您将获得一个名为 Shared\LoginDisplay.razor 的文件,其中包含以下内容:

    <AuthorizeView>
        Hello, @context.User.Identity.Name!
    </AuthorizeView>
    

    使用&lt;AuthorizeView&gt;,我们无需对任何页面进行任何修改即可访问@context.User.Identity.Name

    【讨论】:

    • 我试过这个OOB,但我不知道这个名字。我正在使用 ADB2C 进行身份验证。需要任何 Startup.cs 设置吗?
    【解决方案7】:

    在您的 App.razor 中,确保元素封装在 CascadingAuthenticationState 元素中。如果您创建具有身份验证支持的 Blazor 项目,这是默认生成的。

    </CascadingAuthenticationState>
        <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
            <Found Context="routeData">
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            </Found>
            <NotFound>
                <LayoutView Layout="@typeof(MainLayout)">
                    <p>Sorry, there's nothing at this address.</p>
                </LayoutView>
            </NotFound>
        </Router>
    </CascadingAuthenticationState>
    

    在您的组件中,您可以使用 AuthenticationStateProvider 访问当前用户,如下例所示:

    @page "/"
    
    @layout MainLayout
    
    @inject AuthenticationStateProvider AuthenticationStateProvider
    @inject SignInManager<IdentityUser> SignInManager
    
    @code
    {
        override protected async Task OnInitializedAsync()
        {
            var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    
            if (SignInManager.IsSignedIn(authenticationState.User))
            {
                //Do something...
            }
        }
    }
    

    【讨论】:

      【解决方案8】:

      如果您在 Windows 上的 IIS 或 IIS Express 下运行,则以下解决方案有效。如果使用 'dotnet run' 在 kestrel 下运行,请按照此处的步骤操作,https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.0&tabs=visual-studio#kestrel

      [startup.cs]

      public void ConfigureServices(IServiceCollection services)
      {
          services.AddHttpContextAccessor();
      }
      

      [index.razor]

      @page "/"
      @using Microsoft.AspNetCore.Http
      @inject IHttpContextAccessor httpContextAccessor
      
      <h1>@UserName</h1>
      
      @code {
          public string UserName;
      
          protected override async Task OnInitializedAsync()
          {
              UserName = httpContextAccessor.HttpContext.User.Identity.Name;
          }
      }
      

      【讨论】:

      • 此外,这似乎只适用于“主”页面线程。任何异步操作,包括await,使httpContextAccessor.HttpContext 成为null
      【解决方案9】:

      我也有这个问题。我发现的最佳解决方案是同时注入UserManagerAuthenticationStateProvider,然后我制作了这些扩展函数:

              public static async Task<CustomIdentityUser> GetUserFromClaimAsync(this 
              ClaimsPrincipal claimsPrincipal,
                  UserManager<CustomIdentityUser> userManager)
              {
                  var id = userManager.GetUserId(claimsPrincipal);
                  return await userManager.FindByIdAsync(id);
              }
      
              public static async Task<CustomIdentityUser> GetCurrentUserAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
              {
                  return await (await provider.GetAuthenticationStateAsync()).User.GetUserFromClaimAsync(UM);
              }
      
              public static async Task<string> GetCurrentUserIdAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
              {
                  return UM.GetUserId((await provider.GetAuthenticationStateAsync()).User);
              }
      

      【讨论】:

      • 根据docs here UserManagerSigninManager 在 Razor 组件中不受支持。
      • @aryeh 他们确实工作了,编译没有问题,也没有警告
      • 它也对我有用,我正在尝试找出“不支持”的含义,因为它确实有效。
      【解决方案10】:

      对我来说,追逐移动目标是一段痛苦的旅程。在我的情况下,我只需要在 Razor 页面中使用的 Blazor 组件的用户名。我的解决方案需要以下内容:

      在 Index.cshtml.cs 我添加了两个属性和构造函数

          public IHttpContextAccessor HttpContextAccessor { get; set; }
          public string UserName { get; set; }
      
          public TestModel(IHttpContextAccessor httpContextAccessor)
          {
              HttpContextAccessor = httpContextAccessor;
              if (HttpContextAccessor != null) UserName = HttpContextAccessor.HttpContext.User.Identity.Name;
          }
      

      然后在我添加组件的 Index.cshtml 中,我调用它如下:

      <component type="typeof(MyApp.Components.FileMain)" param-UserName="Model.UserName" render-mode="ServerPrerendered" />
      

      在我的组件中,我使用文件后面的代码(使用公共类 FileMainBase 的 FileMain.razor.cs : ComponentBase)有代码:

          [Parameter]
          public string UserName { get; set; } = default!;
      

      然后作为概念证明,我添加到 FileMain.razor 页面

          <div class="form-group-sm">
          <label class="control-label">User: </label>
          @if (UserName != null)
          {
              <span>@UserName</span>
          }
      </div>
      

      【讨论】:

        【解决方案11】:

        更新 - 这个答案不起作用。我没有将其删除,而是将其作为信息放在这里。请考虑该问题的其他答案。

        1. 在 ConfigureServices 的 Startup.cs 中,添加 services.AddHttpContextAccessor();
        2. 在您的组件类中 [注意:我使用启用了 null 类型的代码隐藏]
                [Inject]
                private IHttpContextAccessor HttpContextAccessor { get; set; } = default!;
        
                private string username = default!;
        
        1. 在您的组件代码(代码后面)中,在protected override void OnInitialized()
                username = HttpContextAccessor.HttpContext.User.Identity.Name;
        

        username 现在可以像任何其他变量一样在整个组件中使用。

        但是,请参阅我在此问题中的其他答案,以添加从可用于任何类的服务中获取当前用户名。

        【讨论】:

        • HttpContext 不应在 Blazor 应用中使用。 HttpContext 是基于 HTTP 的对象。 Blazor Server App(客户端部分和服务器部分)通过 WebSocket 连接进行通信,缺少 HTTP 的概念。使用 Windows 身份验证会使事情变得更糟。我不介意你选择忽略我上面的评论,但必须警告这篇文章的用户,越早越好。
        • 你不知道你在说什么。这与 Windows 身份验证无关。 Blazor 是通过 HTTP 提供服务的,我们所说的它的模型当然是服务器。此外,SignalR 在其他协议中使用 WebSockets,例如长轮询,尽管 WebSockets 可能是典型的
        • 这种方法在 IIS Express 上的开发中有效,但在部署时会导致空值。带有 GetAuthenticationStateAsync 的 AuthenticationStateProvider 工作正常。见Docs
        • 这在部署时不起作用。文档甚至说不要这样做。
        • @MichaelB 我也有同样的问题。您能否建议一种使用AuthenticationStateProvider 作为我所有组件的通用功能的方法来获得索赔?这样,我们就可以避免在每个组件上获取声明...
        【解决方案12】:

        这对我来说在单个页面上有效

        添加到 Startup.cs

        services.AddHttpContextAccessor();
        

        在 Razor 组件页面上

        @using Microsoft.AspNetCore.Http
        @inject IHttpContextAccessor httpContextAccessor
        
        <div>@GetCurrentUser()</div>
        
        @code{
            protected string GetCurrentUser()
            {
                return httpContextAccessor.HttpContext.User.Identity.Name;
            }
        }
        

        【讨论】:

          【解决方案13】:

          您应该在登录后向用户添加所需的声明

          例如,我将 FullName 显示在网站顶部(AuthLinks 组件)而不是电子邮件。

          [HttpPost]
              public async Task<IActionResult> Login(LoginVM model)
              {
                  var user = await _userManager.FindByNameAsync(model.Email);
                  if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
                      return Unauthorized("Email or password is wrong.");
                  var signingCredentials = GetSigningCredentials();
                  var claims = GetClaims(user);
                  var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
                  var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
                  return Ok(new LoginDto { Token = token, FullName = user.FirstName + " " + user.LastName });
              }
          
          @code {
              private LoginVM loginVM = new();
          
              [Inject]
              public AuthenticationStateProvider _authStateProvider { get; set; }
          
              private async Task SubmitForm()
              {
                  var response = await _http.PostAsJsonAsync("api/accounts/login", loginVM);
                  var content = await response.Content.ReadAsStringAsync();
                  var loginDto = JsonSerializer.Deserialize<LoginDto>(content);
                  await _localStorage.SetItemAsync("authToken", loginDto.Token);
                  _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginDto.Token);
                  (_authStateProvider as AuthStateProvider).NotifyLogin(loginDto.FullName);
                  _navigationManager.NavigateTo("/");
              }
          }
          
          public void NotifyLogin(string fullName)
              {
                  var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("FullName", fullName) }, "jwtAuthType"));
                  var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
                  NotifyAuthenticationStateChanged(authState);
              }
          
          <AuthorizeView>
              <Authorized>
                  @context.User.FindFirst("FullName")?.Value
                  <button class="btn btn-outline-danger mx-4" @onclick="LogOut">LogOut</button>
              </Authorized>
              <NotAuthorized>
                  <a href="account/login" class="btn btn-outline-success mx-2">Login</a>
                  <a href="account/register" class="btn btn-outline-primary mx-2" style="width:123.44px">Register</a>
              </NotAuthorized>
          </AuthorizeView>
          

          GitHub 项目链接: https://github.com/mammadkoma/Attendance

          【讨论】:

            【解决方案14】:

            这对我有用:

            替换:

            @context.User.Identity.Name
            

            与:

            @context.User.Claims.Where(x => x.Type=="name").Select(x => x.Value).FirstOrDefault()
            

            【讨论】:

              猜你喜欢
              • 2018-10-10
              • 2020-11-16
              • 1970-01-01
              • 2019-05-30
              • 1970-01-01
              • 2021-02-24
              • 2012-04-08
              • 1970-01-01
              相关资源
              最近更新 更多