【问题标题】:Blazor WASM Hosted - Get UserId in both Client and API/RepositoryBlazor WASM 托管 - 在客户端和 API/存储库中获取 UserId
【发布时间】:2021-06-22 09:04:35
【问题描述】:

我有一个 Blazor WASM 托管解决方案,该解决方案分为几个项目。客户端显然是客户端 WASM,服务器是托管 API 控制器的服务器端,数据是我的数据访问,具有 DataContext、存储库、迁移、应用程序用户等,然后是完全只是模型的模型。它使用身份作为其身份验证/授权

我想要完成的是两件事

1:我想在每次保存更新时记录用户 ID。

public async Task<Country> CreateAsync(Country country)
        {
            try
            {
                var addedEntity = _dataContext.Countries.Add(country);
                await _dataContext.SaveChangesAsync();
                return addedEntity.Entity;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in CreateAsync()");
                throw;
            }
        }

在这里id喜欢做country.CreatedBy = userId; (这是在数据项目中)如何获取用户信息?为了访问这部分代码,他们必须获得授权。这是控制器操作,该控制器由 [Authorize] 属性管理

public async Task<IActionResult> Create(Country country)
        {
            if (country == null)
            {
                return BadRequest();
            }

            try
            {
                var createdCountry = await _repository.CreateAsync(country);
                return CreatedAtAction("GetById", new { id = createdCountry.Id }, createdCountry);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Create() - Returning Internal Server Error");
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

我还希望能够在控制器级别访问用户 ID(这是在服务器项目中)。我最终想添加更多日志并将 UserId 包含在日志中。

这是我的数据上下文

public class DataContext : ApiAuthorizationDbContext<ApplicationUser>
    {
        public DataContext(DbContextOptions options, IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
        {
            ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        }
**db sets omitted**
}
  1. 在我的客户端项目中,我还希望能够访问组件中的 UserId 以进行日志记录。这是我的 App.razor 使用 CascadingAuthenticationState
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly"  AdditionalAssemblies="new[] { typeof(BBQFriend.Components.PageComponents.Index).Assembly }">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>You are not authorized to access this resource.</p>
                    }
                </NotAuthorized>
                <Authorizing>
                    <Loading />
                </Authorizing>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <div class="content">
                    <div class="section-title">
                        <h1>I think you're lost...</h1>
                        <div class="section-title-stripe"></div>
                    </div>
                </div>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

在我的组件中我可以

[Inject]
public AuthenticationStateProvider AuthenticationStateProvider { get; set; }

protected override async Task OnInitializedAsync() 
{
   var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();

}

可以从 AuthenticationState 访问用户,但是我只能从 authenticationState.User.Identity.Name 获取名称,但我想获取 UserId。我可以将 UserId 添加到身份中,还是有办法在客户端获取 UserId?这是在客户端项目中,所以我没有任何像 UserManager 或 SigninManager 这样的身份类,它们在服务器上。

更新 1 我试过添加 services.AddHttpContextAccessor();到 Server Startup.cs ConfigureServices 方法并尝试在 Repository 级别(在 Data 项目中)使用 DI

public IngredientRepository(DataContext dataContext, ILogger<IngredientRepository> logger, IHttpContextAccessor httpContextAccessor)
        {
            _dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
            _logger = logger;
            _httpContextAccessor = httpContextAccessor;

            if(_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
            {
                userName = _httpContextAccessor.HttpContext.User.Identity.Name;
                userId = _httpContextAccessor.HttpContext.User.FindFirst(c => c.Type == "sub")?.Value;
            }           
        }

此上下文中的身份是 IsAuthenticated = True 但不包含有关用户的信息。没有声明、角色、名称等。

【问题讨论】:

    标签: asp.net-identity blazor webassembly


    【解决方案1】:

    我能够通过在组件的 OnInitialize 中执行以下操作来获取客户端上的 UserId。我仍然需要弄清楚如何在 API 控制器和存储库级别获取服务器端的 UserId 和 Name

    var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authenticationState.User;
    
    if(user.Identity.IsAuthenticated)
    {
       userId = user.FindFirst(c => c.Type == "sub")?.Value;
       userName = user.Identity.Name;
    }
    
    

    【讨论】:

    • 你成功了吗?我刚刚将一个工作正常的 .Net 5、开箱即用的 MS Identity Wasm 应用程序迁移到了 Net 6。新的 Blazor 模板使用了 Duende Identity Server(而不是基本的 MS Identity),所以这并不好。我必须使用 Chris Sainty 的博客文章才能使其正常工作,但我还需要访问 Wasm 控制器和用户中的登录使用。身份从不给我 ID 或电子邮件。 UserManager 也没有得到它。在另一篇文章中,我添加了以下内容 - 没有更改:builder.Services.Configure(opt => opt.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier
    • 我没有升级到 .net6,因为 duende 身份服务器升级破坏了我的整个网站。
    【解决方案2】:

    在客户端使用 Auth 很好。但是,这应该仅用于控制可见性和对客户端功能的访问。

    为了保护您的 API,请使用 [Authorize] 属性标记您的 API 方法或整个控制器

    [Authorize]
    public class WeatherForecastController : ControllerBase 
    {
        ...
    

    向控制器添加构造函数

           
        UserManager<ApplicationUser> userManager;
    
        public WeatherForecastController(UserManager<ApplicationUser> userManager)
        {           
            this.userManager = userManager;
        }
    

    然后在你的控制器方法中调用这个函数:

    private async Task<ApplicationUser> GetUser()
    {
        ApplicationUser user = default;
    
        if (User.Identity.IsAuthenticated)
        {
            var userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
                  
            user = await userManager.FindByIdAsync(userId);
        }
    
        return user;
    }
    
    

    Here 是我从模板中创建的一个仓库。 有关角色和策略的完整示例,我有一个 repo here

    【讨论】:

    • 你所说的部分是相关的,有些是不相关的。正如我提到的,我的应用程序是带有身份的 Blazor WASM 托管解决方案。因此,Server 项目是一个为 Blazor WASM 解决方案提供服务的 .netcore webapi。服务器项目是 WebAPI 控制器所在的位置。这是 VS2019 的开箱即用安装。该应用程序使用带有 EF 的 DefaultIdentity 和 IdentityServer 作为授权和身份验证。您提出的在控制器中注入用户管理器并在控制器中访问用户的建议在 APIController 中不起作用,因为用户为空。
    • @JoeyD 对不起,我会更新我的代码。我从中获取的存储库设置为将电子邮件作为声明传递。我将更新我的答案以使用“开箱即用”自托管。
    • 所以在那种情况下,我可以看到用户有声明,但在声明中根本没有 NameIdentifier。 User.Identity.Name 为空。所以我假设我需要添加 .AddClaimsPrincipalFactory();您在 AuthApp 示例中所拥有的
    • @JoeyD 我不必在新的仓库中这样做。我只从模板中对控制器进行了更改。我在控制器中放置了一个断点并检查了User 属性。我只花了几分钟就完成了那个回购。我做了一些小改动。
    【解决方案3】:

    要从控制器中获取用户 ID 和名称,您需要使用控制器的 HttpContext 属性。我喜欢这样做的方式是创建扩展方法:

    public static string GetUserId(this HttpContext httpContext)
        {
            if (httpContext.User is null)
            {
                return string.Empty;
            }
            return httpContext.User.Claims.Single(x => x.Type == ClaimTypes.NameIdentifier).Value;
        }
    
        public static string GetUserName(this HttpContext httpContext)
        {
            if (httpContext.User is null)
            {
                return string.Empty;
            }
    
            return httpContext.User.Claims.Single(x => x.Type == ClaimTypes.Name).Value;
        }
    

    然后你可以在你的控制器中调用它们:

     var userId = HttpContext.GetUserId();
     var userName = HttpContext.GetUserName();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-27
      • 2021-02-03
      • 2020-07-22
      • 1970-01-01
      • 2020-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多