获取已登录用户的身份?

huangapple go评论56阅读模式
英文:

Getting the identity of the logged in user?

问题

The is for a Blazor (server side) application. As I understand it, I have three objects for a logged-in user:

  • AuthenticationState which I can get in my razor page as it's injected. This allows me to make calls to see if claims allow users to take actions or see data.
  • IdentityUser which is the ASP.NET Identity user which has their email, phone, etc. I can get that using the code below.

User which is my app user object. User.AspUserId == IdentityUser.Id so I can read that object once I have the IdentityUser.

private async Task<IdentityUser?> GetUser()
{
    var userPrincipal = (await authenticationStateTask).User;

    var userid = userPrincipal
                     .FindFirst(u => u.Type.Contains("nameidentifier"))?
                     .Value;

    if (!string.IsNullOrEmpty(userid))
        return await UserDbContext.Users.FirstOrDefaultAsync(u => u.Id == userid);

    return null;
}

My questions are:

  1. Are these three objects what I should be using?
  2. There's an AuthenticationStateProvider.AuthenticationStateChanged event. Should I set this up and use it to re-read my IdentityUser and User object, and otherwise I can keep those objects without re-reading them per page.
  3. If so, where do I store these objects as they're a per-session object.
  4. Is my GetUser() code above the best way to get the IdentityUser?

Anything I'm missing in all this?

英文:

The is for a Blazor (server side) application. As I understand it, I have three objects for a logged in user:

  • AuthenticationState which I can get in my razor page as it's injected. This allows me to make calls to see if claims allow users to take actions or see data.
  • IdentityUser which is the ASP.NET Identity user which has their email, phone etc. I can get that using the code below.
  • User which is my app user object. User.AspUserId == IdentityUser.Id so I can read that object once I have the IdentityUser.
private async Task&lt;IdentityUser?&gt; GetUser()
{
    var userPrincipal = (await authenticationStateTask).User;

    var userid = userPrincipal
                     .FindFirst(u =&gt; u.Type.Contains(&quot;nameidentifier&quot;))?
                     .Value;

    if (!string.IsNullOrEmpty(userid))
        return await UserDbContext.Users.FirstOrDefaultAsync(u =&gt; u.Id == userid);

    return null;
}

My questions are:

  1. Are these three objects what I should be using?
  2. There's a AuthenticationStateProvider.AuthenticationStateChanged event. Should I set this up and use it to re-read my IdentityUser and User object and otherwise I can keep those objects without re-reading them per page.
  3. If so, where do I store these objects as they're a per session object.
  4. Is my GetUser() code above the best way to get the IdentityUser?

Anything I'm missing in all this?

答案1

得分: 1

为了回答你的问题,你需要考虑在哪里需要使用当前用户的上下文。

在组件中,这相当简单。你可以使用Task&lt;AuthenticationState&gt;级联参数来获取。它是一个Task,但几乎总是处于Completed状态,并立即返回。

然而,如果你需要在另一个服务中使用用户上下文,你需要使用注册的AuthenticationStateProvider服务。

以下是一个简单的用户包装服务,可以被服务或组件使用。

public class UserService : IDisposable
{
    private readonly AuthenticationStateProvider _stateProvider;
    private Task _task = Task.CompletedTask;

    public event EventHandler&lt;UserChangedEventArgs&gt;? UserChanged;

    public ClaimsPrincipal User { get; set; } = new ClaimsPrincipal();

    public UserService(AuthenticationStateProvider authenticationStateProvider)
    {
        _stateProvider = authenticationStateProvider;
        _stateProvider.AuthenticationStateChanged += this.OnUserChanged;
        // 获取当前用户。由于这是一个异步方法,我们只需将其分配给一个类变量
        _task = this.GetUserAsync(_stateProvider.GetAuthenticationStateAsync());
    }

    public async ValueTask&lt;ClaimsPrincipal&gt; GetUserAsync()
    {
        // 可能已完成,但我们不能确定,所以总是等待它
        await _task;
        return User;
    }

    private void OnUserChanged(Task&lt;AuthenticationState&gt; task)
    {
        // 由于这是一个fire and forget事件,我们将新任务分配给我们的类任务
        // 如果需要的话,我们可以等待它
        _task = this.GetChangedUserAsync(task);
    }

    private async Task GetChangedUserAsync(Task&lt;AuthenticationState&gt; task)
    {
        // 获取用户
        await this.GetUserAsync(task);
        // 获取新用户后,触发fire and forget事件
        this.UserChanged?.Invoke(this, new UserChangedEventArgs(User));
    }

    private async Task GetUserAsync(Task&lt;AuthenticationState&gt; task)
    {
        // 将用户重置为新的ClaimsPrincipal,实际上是没有用户的
        this.User = new ClaimsPrincipal();
        
        // 获取状态对象
        var state = await task;

        // 更新用户
        if (state is not null)
            this.User = state.User;
    }

    public void Dispose()
         =&gt; _stateProvider.AuthenticationStateChanged -= this.OnUserChanged;
}

public class UserChangedEventArgs : EventArgs
{
    public ClaimsPrincipal? User { get; private set; }

    public UserChangedEventArgs(ClaimsPrincipal? user)
        =&gt; User = user;
}

供参考,这是CascadingAuthenticationState的代码:

@implements IDisposable
@inject AuthenticationStateProvider AuthenticationStateProvider

&lt;CascadingValue TValue=&quot;System.Threading.Tasks.Task&lt;AuthenticationState&gt;&quot; Value=&quot;@_currentAuthenticationStateTask&quot; ChildContent=&quot;@ChildContent&quot; /&gt;

@code {
    private Task&lt;AuthenticationState&gt;? _currentAuthenticationStateTask;

    /// &lt;summary&gt;
    /// 应提供身份验证状态的内容。
    /// &lt;/summary&gt;
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;

        _currentAuthenticationStateTask = AuthenticationStateProvider
            .GetAuthenticationStateAsync();
    }

    private void OnAuthenticationStateChanged(Task&lt;AuthenticationState&gt; newAuthStateTask)
    {
        _ = InvokeAsync(() =&gt;
        {
            _currentAuthenticationStateTask = newAuthStateTask;
            StateHasChanged();
        });
    }

    void IDisposable.Dispose()
    {
        AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
    }
}

你可以在这里看到原始代码 - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/CascadingAuthenticationState.razor

英文:

To answer all your questions, you need to consider where you need to consume the current user.

In a component, that's pretty simple. You can consume the Task&lt;AuthenticationState&gt; cascaded parameter. It's a Task, but will almost always be in the Completed state, and return immediately.

If however you need the user context in another service you need to use the registered AuthenticationStateProvider service.

Here's a simple User wrapper Service that can be consumed by Services or components.

public class UserService : IDisposable
{
    private readonly AuthenticationStateProvider _stateProvider;
    private Task _task = Task.CompletedTask;

    public event EventHandler&lt;UserChangedEventArgs&gt;? UserChanged;

    public ClaimsPrincipal User { get; set; } = new ClaimsPrincipal();

    public UserService(AuthenticationStateProvider authenticationStateProvider)
    {
        _stateProvider = authenticationStateProvider;
        _stateProvider.AuthenticationStateChanged += this.OnUserChanged;
        // get the current user. as this is an async method we just assign it to a class variable
        _task = this.GetUserAsync(_stateProvider.GetAuthenticationStateAsync());
    }

    public async ValueTask&lt;ClaimsPrincipal&gt; GetUserAsync()
    {
        // probably complete, but we don&#39;t know for sure so always await it
        await _task;
        return User;
    }

    private void OnUserChanged(Task&lt;AuthenticationState&gt; task)
    {
        // As this is a fire and forget event we assign the new task to our class task
        // which we can await if we need to
        _task = this.GetChangedUserAsync(task);
    }

    private async Task GetChangedUserAsync(Task&lt;AuthenticationState&gt; task)
    {
        // get the user
        await this.GetUserAsync(task);
        // raise our fire and forget event now we&#39;ve got the new user
        this.UserChanged?.Invoke(this, new UserChangedEventArgs(User));
    }

    private async Task GetUserAsync(Task&lt;AuthenticationState&gt; task)
    {
        // Reset the user to a new ClaimsPrincipal which is effectivity no user
        this.User = new ClaimsPrincipal();
        
        // get the state object
        var state = await task;

        // update the user
        if (state is not null)
            this.User = state.User;
    }

    public void Dispose()
         =&gt; _stateProvider.AuthenticationStateChanged -= this.OnUserChanged;
}

public class UserChangedEventArgs : EventArgs
{
    public ClaimsPrincipal? User { get; private set; }

    public UserChangedEventArgs(ClaimsPrincipal? user)
        =&gt; User = user;
}

For reference, here's the code for CascadingAuthenticationState:

@implements IDisposable
@inject AuthenticationStateProvider AuthenticationStateProvider

&lt;CascadingValue TValue=&quot;System.Threading.Tasks.Task&lt;AuthenticationState&gt;&quot; Value=&quot;@_currentAuthenticationStateTask&quot; ChildContent=&quot;@ChildContent&quot; /&gt;

@code {
    private Task&lt;AuthenticationState&gt;? _currentAuthenticationStateTask;

    /// &lt;summary&gt;
    /// The content to which the authentication state should be provided.
    /// &lt;/summary&gt;
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;

        _currentAuthenticationStateTask = AuthenticationStateProvider
            .GetAuthenticationStateAsync();
    }

    private void OnAuthenticationStateChanged(Task&lt;AuthenticationState&gt; newAuthStateTask)
    {
        _ = InvokeAsync(() =&gt;
        {
            _currentAuthenticationStateTask = newAuthStateTask;
            StateHasChanged();
        });
    }

    void IDisposable.Dispose()
    {
        AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
    }
}

You see see the original here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/CascadingAuthenticationState.razor

huangapple
  • 本文由 发表于 2023年5月7日 16:40:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76192925.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定