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

ASP.NET Core 6如何获取AspNetUsers表StoreId实现身份校验

登录时StoreId匹配校验实现方案

你可以按照以下步骤完成需求,所有修改都基于现有代码结构,不需要重构原有身份认证逻辑:

  • 第一步:给LoginModel的StoreId字段增加必填校验
    现有模型里的StoreId是可空类型且没有校验规则,容易出现空值提交绕过校验的问题,先补充数据注解:
public class LoginModel
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }

    public IList<AuthenticationScheme>? ExternalLogins { get; set; }
    public string? ReturnUrl { get; set; }
    public string? ErrorMessage { get; set; }
    
    [Required(ErrorMessage = "门店标识缺失,禁止登录")]
    public string? StoreId { get; set; }
}
  • 第二步:修改Login接口的校验逻辑,在密码校验前增加StoreId匹配判断
    首先确认ShopController已经注入了UserManager<你的用户实体类>,如果没有自定义Identity用户类,默认用IdentityUser即可(需要保证用户实体类中已经定义了StoreId属性,和数据库表字段对应)。
    不要在密码校验通过后直接跳转,先查询对应用户的StoreId做比对,不匹配直接返回登录错误,避免越权登录。修改后的Login方法核心代码如下:
// 还没注入UserManager的话,补上构造函数注入
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<ShopController> _logger;

public ShopController(SignInManager<IdentityUser> signInManager, 
    ILogger<ShopController> logger, 
    UserManager<IdentityUser> userManager)
{
    _signInManager = signInManager;
    _logger = logger;
    _userManager = userManager;
}

public async Task<IActionResult> Login(LoginModel model)
{
    model.ReturnUrl ??= Url.Content("~/Customer/Shop/");
    model.ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    if (ModelState.IsValid)
    {
        // 先根据提交的邮箱查询用户,提前做StoreId校验,减少无效密码校验的开销
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user == null)
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }

        // 比对请求携带的StoreId和用户数据库中绑定的StoreId
        // 如果StoreId是int/Guid类型,先做类型转换再比对即可
        if (user.StoreId != model.StoreId)
        {
            _logger.LogWarning("账号{Email}尝试在非授权门店{StoreId}登录,已拦截", model.Email, model.StoreId);
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }

        // StoreId校验通过后,再走原有密码、双因子、锁定判断逻辑
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            _logger.LogInformation("账号{Email}在门店{StoreId}登录成功", model.Email, model.StoreId);
            return LocalRedirect(model.ReturnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new { ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("账号{Email}已被锁定", model.Email);
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }
    }

    return View(model);
}
  • 第三步(可选但强烈推荐):增加全局请求的StoreId校验
    仅在登录入口做校验无法避免用户登录后篡改门店标识的越权行为,可以实现一个全局Action过滤器,对所有已登录的请求自动校验StoreId是否匹配,不匹配则自动登出跳转登录页:
public class StoreIdValidateFilter : IAsyncActionFilter
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly SignInManager<IdentityUser> _signInManager;

    public StoreIdValidateFilter(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // 未登录请求直接放行,走默认认证逻辑
        if (!context.HttpContext.User.Identity.IsAuthenticated)
        {
            await next();
            return;
        }

        // 从请求中获取当前门店标识,可以根据自己的业务存储位置调整取值逻辑,比如从Header、Cookie、路由中取
        var currentStoreId = context.HttpContext.Request.Form["StoreId"].FirstOrDefault()
                             ?? context.HttpContext.Request.Query["StoreId"].FirstOrDefault()
                             ?? context.HttpContext.Request.Cookies["CurrentStoreId"];

        if (string.IsNullOrWhiteSpace(currentStoreId))
        {
            await _signInManager.SignOutAsync();
            context.Result = new RedirectResult("~/Customer/Shop/Login");
            return;
        }

        var currentUserId = _userManager.GetUserId(context.HttpContext.User);
        var currentUser = await _userManager.FindByIdAsync(currentUserId);
        if (currentUser == null || currentUser.StoreId != currentStoreId)
        {
            await _signInManager.SignOutAsync();
            context.Result = new RedirectResult("~/Customer/Shop/Login");
            return;
        }

        await next();
    }
}

在Program.cs中注册全局过滤器即可生效:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<StoreIdValidateFilter>();
});

注意事项

  • 所有登录失败的提示统一返回「Invalid login attempt」,不要明确提示「门店不匹配」,避免攻击者枚举系统有效账号
  • 前端隐藏域传值可以被用户随意篡改,所有校验逻辑必须放在后端实现,不要依赖前端传值的安全性
  • 如果StoreId是数值、Guid类型,比对前一定要先做格式合法性校验,避免类型转换触发异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 03:06:28