ASP.NET Core Web API 中的自定义授权过滤器

问题描述 投票:0回答:1

我有一个 ASP.NET Core 6.0 Web API,它接收来自 Angular Web UI 应用程序的请求。目前我已经按照以下方式实现了 JWT 令牌授权:[https://www.c-sharpcorner.com/article/jwt-token-authentication-and-authorizations-in-net-core-6-0-web-api/ ][1].

我想要实现的目标是:用户应该仅登录单个设备。如果同一用户尝试从第二个设备登录,则应从第一个设备注销,然后登录到第二个设备。来自第一台设备的所有 API 请求都应未经授权。

要实现这一点:

  1. 我需要实现已实现的访问令牌和刷新令牌。
  2. 当用户尝试从第二个设备登录时,我需要使第一个设备的令牌无效。

根据我的理解,第2点可以使用Web API中的自定义授权过滤器来植入。任何人都可以建议流程是否正确,如果有这种实现的示例,那就太好了。

c# asp.net-core filter authorization asp.net-core-webapi
1个回答
0
投票

任何人都可以建议流程是否正确,如果有的话那就太好了 这种实现的示例

好吧,如果您能够分享一些现有的代码片段,那么您目前的情况会更好。

然而,这个过程有点争议,因为它可以通过多种方式实现。

我不太确定您的身份验证是什么样的。但我试图分享如何使用会话和 asp.net core Identity 来实现它。

正如我已经说过的,正如有人可能会说的那样,这个过程有点争论,他们可以使用用户声明或使用数据库交互来做到这一点。

即使可能存在争论,我将如何定义设备的唯一性,所以我使用了一个类,在其中考虑用户浏览器信息、MAC 地址、UserId 或 IP。

使用上述两者中的任何一个,我认为我们可以定义设备身份。

好吧,让我们看看如何实现:

我是如何规划算法的:

首先,我发送用户电子邮件和登录密码,我在其中获取用户浏览器信息、用户 MAC 和 IP 地址。

我们来看看:

private async Task<DeviceInfo> GetCurrentDeviceIdentifier(HttpContext httpContext)
  {
      
      var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
    
      using (var client = new HttpClient())
      {
          var response = await client.GetStringAsync("https://api64.ipify.org?format=json");

         
          var ipAddress = JObject.Parse(response)["ip"].ToString();
         
          var combinedIdentifier = $"{ipAddress}_{userAgent}";
          //var hashedIdentifier = ComputeHash(combinedIdentifier);

          var _deviceInfo = new DeviceInfo();

          _deviceInfo.BrowserInfo = userAgent;
          _deviceInfo.MAC = GetMacAddress();
          return _deviceInfo;
      }
  }

注意:为了更好的安全性,我们可以将设备信息放入哈希中。但我要跳过演示。

检查用户登录:

var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
string? userId = _userManager.GetUserId(User);
DeviceInfo deviceIdentifier = await GetCurrentDeviceIdentifier(HttpContext);
deviceIdentifier.UserId = userId!;
var signInStatus = await GetDeviceIdInSession(deviceIdentifier);
if (signInStatus == "Authorized")
{

    if (result.Succeeded)
    {
        _logger.LogInformation("User logged in.");
        return LocalRedirect(returnUrl);
    }
    if (result.RequiresTwoFactor)
    {
        return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
    }
    if (result.IsLockedOut)
    {
        _logger.LogWarning("User account locked out.");
        return RedirectToPage("./Lockout");
    }
    else
    {
        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return Page();
    }
}
else
{
    ModelState.AddModelError(string.Empty, $"Invalid login attempt. User already login in device {deviceIdentifier.MAC} browser info: {deviceIdentifier.BrowserInfo} ");
    return Page();
}

在会话中保留设备信息:

private async Task<bool> SetDeviceInfoInSession(DeviceInfo deviceInfo)

{
    var userDevice = HttpContext.Session.GetComplexObjectSession<DeviceInfo>("DeviceInfo");
    if (userDevice == null)
    {
        HttpContext.Session.SetComplexObjectSession("DeviceInfo", deviceInfo);
        return true;
    }
    else
    {
        return false;
    }


}
private async Task<string> GetDeviceIdInSession(DeviceInfo deviceInfo)
{
    var loginUserDevice = HttpContext.Session.GetComplexObjectSession<DeviceInfo>("DeviceInfo");
    string authStatus = "";
    if (loginUserDevice == null)
    {
       await SetDeviceInfoInSession(deviceInfo);
       authStatus = "Authorized";
    }
    else
    {
        if (deviceInfo.MAC == loginUserDevice!.MAC || deviceInfo.BrowserInfo == loginUserDevice!.BrowserInfo)
        {
            authStatus = "Unauthorized";
        }
        else
        {
            authStatus = "Authorized";
        }
    }

    return authStatus;

}

我用过的课程:

public class DeviceInfo
  {
      public string UserId { get; set; }
      public string? MAC { get; set; }
      public string? BrowserInfo { get; set; }
  }

注意:如果需要任何其他信息,您可以修改课程

会话处理程序方法:

public static class SessionExtension
{
    //setting session
    public static void SetComplexObjectSession(this ISession session, string key, object value)
    {
        session.SetString(key, JsonConvert.SerializeObject(value));
    }

    //getting session
    public static T? GetComplexObjectSession<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
    }
}

程序.cs:

builder.Services.AddSession(options =>
{
    options.Cookie.Name = "DeviceInfo";
    options.IdleTimeout = TimeSpan.FromMinutes(3);
    options.Cookie.IsEssential = true;
});

app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();

输出:

注意: 您还可以使用用户声明或基于策略的身份验证来做到这一点。另一件事是,它的讨论有点长,所以在一个问题中很难在没有相关代码片段的情况下解释所有内容。我建议您发布新问题以及您的具体问题和相关代码片段。希望你明白我的意思。

© www.soinside.com 2019 - 2024. All rights reserved.