You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server Cookie认证下服务层安全获取当前登录用户方案

在Blazor Server Cookie认证下安全获取用户ID用于审计日志的方案

问题背景

在Blazor Server项目中使用Cookie认证,需要在HotelRoomService.cs的DeleteHotelRoomAsync方法中获取当前登录用户ID,写入数据库审计日志。遇到的问题:

  • AuthenticationStateProvider实例为空,无法直接使用
  • 使用IHttpContextAccessor不符合微软官方建议,但能获取到用户ID

推荐解决方案

方案1:从Blazor组件层传递用户ID到服务层(最推荐)

Blazor组件可以正常通过AuthenticationStateProvider获取用户身份,在调用服务方法时直接传入用户ID,避免服务层依赖请求上下文,保持服务的独立性和可测试性。

组件端代码:

@inject AuthenticationStateProvider AuthStateProvider
@inject HotelRoomService HotelRoomService

// 调用删除方法的逻辑
private async Task HandleDelete(int roomId)
{
    var authState = await AuthStateProvider.GetAuthenticationStateAsync();
    var userId = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    
    if (!string.IsNullOrEmpty(userId))
    {
        await HotelRoomService.DeleteHotelRoomAsync(roomId, userId);
    }
}

服务层修改后的DeleteHotelRoomAsync方法:

public async Task<int> DeleteHotelRoomAsync(int roomId, string userId)
{
    var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId);
    if (roomDetails == null)
    {
        return 0;
    }

    _dbContext.HotelRooms.Remove(roomDetails);
    // 写入审计日志
    _dbContext.DbLog.Add(new DbLog 
    { 
        UserId = userId, 
        RoomId = roomId, 
        OperationTime = DateTime.UtcNow,
        OperationType = "DeleteRoom"
    });
    return await _dbContext.SaveChangesAsync();
}

方案2:使用Scoped服务封装用户上下文

创建一个Scoped的用户上下文服务,在根组件(如MainLayout)中初始化用户信息,服务层通过注入该上下文获取用户ID。

1. 创建UserContext服务:

public class UserContext
{
    public string UserId { get; set; }
}

2. 注册服务:

在Program.cs中添加:

builder.Services.AddScoped<UserContext>();

3. 在根组件中初始化用户信息:

@inject AuthenticationStateProvider AuthStateProvider
@inject UserContext UserContext

@code {
    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
        UserContext.UserId = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    }
}

4. 服务层使用UserContext:

private readonly AppDbContext _dbContext;
private readonly UserContext _userContext;

public HotelRoomService(AppDbContext dbContext, UserContext userContext)
{
    _dbContext = dbContext;
    _userContext = userContext;
}

public async Task<int> DeleteHotelRoomAsync(int roomId)
{
    var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId);
    if (roomDetails == null)
    {
        return 0;
    }

    _dbContext.HotelRooms.Remove(roomDetails);
    // 使用UserContext中的用户ID写入日志
    if (!string.IsNullOrEmpty(_userContext.UserId))
    {
        _dbContext.DbLog.Add(new DbLog 
        { 
            UserId = _userContext.UserId, 
            RoomId = roomId 
        });
    }
    return await _dbContext.SaveChangesAsync();
}

方案3:安全使用IHttpContextAccessor(不推荐但可行)

如果必须在服务层直接获取,需确保只在请求范围内使用,并正确校验用户身份和Claim,避免直接取第一个Claim的错误写法。

1. 注册IHttpContextAccessor:

builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IGetUserId, GetUserId>();

2. 实现IGetUserId接口:

public interface IGetUserId
{
    string Get();
}

public class GetUserId : IGetUserId
{
    private readonly IHttpContextAccessor _contextAccessor;

    public GetUserId(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public string Get()
    {
        var httpContext = _contextAccessor.HttpContext;
        if (httpContext == null || !httpContext.User.Identity.IsAuthenticated)
        {
            return null; // 或根据业务需求抛出未授权异常
        }
        // 正确获取用户ID Claim,替换为你实际使用的Claim类型
        return httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    }
}

3. 服务层使用:

private readonly IGetUserId _getUserId;

public HotelRoomService(AppDbContext dbContext, IGetUserId getUserId)
{
    _dbContext = dbContext;
    _getUserId = getUserId;
}

public async Task<int> DeleteHotelRoomAsync(int roomId)
{
    var roomDetails = await _dbContext.HotelRooms.FindAsync(roomId);
    if (roomDetails == null)
    {
        return 0;
    }

    _dbContext.HotelRooms.Remove(roomDetails);
    var userId = _getUserId.Get();
    if (!string.IsNullOrEmpty(userId))
    {
        _dbContext.DbLog.Add(new DbLog { UserId = userId, RoomId = roomId });
    }
    return await _dbContext.SaveChangesAsync();
}

方案对比

  • 方案1:符合微软设计原则,服务层无请求上下文依赖,可测试性强,是最优选择。
  • 方案2:适合需要在多个服务中复用用户信息的场景,需确保组件初始化逻辑覆盖所有用户路径。
  • 方案3:仅适合特殊场景使用,需注意HttpContext在Blazor Server的SignalR连接中可能不存在,存在线程安全风险。

内容的提问来源于stack exchange,提问作者Sadabadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 23:45:42