c# - 如何防止用户登录多个设备以及如何从之前的设备结束 session ?

标签 c# asp.net asp.net-core asp.net-identity

如何防止一个用户 ID 一次只能登录一台计算机?例如,用户“Sam”登录到他的桌面。用户“Sam”也从他们的笔记本电脑登录。现在我需要终止桌面上的 session ,或者我想要发生的是使旧 token 在桌面上无效。

我正在使用 dotnet 5.0

您是否需要在数据库的“用户”表中使用名为 currentlyloggedinUser 的列来查看他们是否已使用 bool 值登录?

您还需要安全印章吗?

下面是放在AuthController中的登录方法

[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto userForLoginDto)
{

    try 
    {
        // does the user exist in the database
        var user = await _userManager.FindByNameAsync(userForLoginDto.Username);
        // does the password match
        var result = await _signInManager.CheckPasswordSignInAsync(user, userForLoginDto.Password, true);

        //if the users are logged in, store True Value in the database 
            
            user.nonconcurrent = true; 
            var noLoggedin = await _userManager.UpdateAsync(user);

        if(!result.IsLockedOut)
        {
            if(user.IsEnabled)
            {
                if (result.Succeeded)
                {   
                    var userToReturn = _mapper.Map<UserForReturnDto>(user); 

                    return Ok(new
                    {
                        token = GenerateJwtToken(user).Result,
                        user = userToReturn,                 
                    });
                }
                return Unauthorized("Login Failed"); //if username and password are incorrect return unauthorised
            }
            return Unauthorized("This account is disabled. Please get an administrator to unlock this account.");                               
        }                
        return Unauthorized("This account is locked out. Please try again in 10 minutes or get an " + 
            "administrator to unlock your account.");              
    }
    catch (ArgumentNullException) 
    {
        return Unauthorized("Login Failed");
    }
}

下面是StartUp.cs的一部分

 namespace Schedular.API
{
    public class Startup
    {
      public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // services.AddDbContext<DataContext>(x => x.UseMySql(Configuration
            //     .GetConnectionString("DefaultConnection")));

            services.AddDbContext<DataContext>(x => x.UseMySql(Configuration
                .GetConnectionString("DefaultConnection"),
                        new MySqlServerVersion(new Version(8, 0, 21)), 
                        mySqlOptions => mySqlOptions
                            .CharSetBehavior(CharSetBehavior.NeverAppend)));
 
            var lockoutOptions = new LockoutOptions()
            {
                AllowedForNewUsers = true,
                DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10),
                MaxFailedAccessAttempts = 5
            };
                
            // for role and identity authentication
            // initial creation of user in the user table in database 
            IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
            {
                // password requirements
                opt.Lockout = lockoutOptions;
                opt.Password.RequireDigit = true;
                opt.Password.RequiredLength = 8;
                opt.Password.RequireNonAlphanumeric = false;
                opt.Password.RequireUppercase = true;
            });

            builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
            builder.AddEntityFrameworkStores<DataContext>();
            builder.AddRoleValidator<RoleValidator<Role>>();
            builder.AddRoleManager<RoleManager<Role>>();
            builder.AddSignInManager<SignInManager<User>>();

            
            // allows api to use authentication 
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => 
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
                            .GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
                        ValidateIssuer = false,
                        ValidateAudience = false
                    };
                });  


            // authorisation policy
            services.AddAuthorization(options =>
            {
                options.AddPolicy("AdminAccess", policy => policy.RequireRole("Admin"));
                options.AddPolicy("everyone", policy => policy.RequireRole("Admin", "standard"));
            });

            services.AddControllers(options => 
            {
                var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
                
                options.Filters.Add(new AuthorizeFilter(policy));
            })
             .AddNewtonsoftJson(opt =>
            {
                opt.SerializerSettings.ReferenceLoopHandling = 
                Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            }); 
            
            services.AddScoped<ITaskScheduleRepository, TaskScheduleRepository>();
            services.AddScoped<IUserRepository, UserRepository>();
            services.AddScoped<INotesRepository, NotesRepository>();
            services.AddScoped<IAttachmentFileRepository, AttachmentFileRepository>();
            services.AddScoped<ICustomerRepository, CustomerRepository>();
            services.AddScoped<IReportRepository, ReportRepository>();
            services.AddControllers();
            services.AddCors(); 
        


            //allows use of tokens
  

            // Auto Mapper Configurations
            var mappingConfig = new MapperConfiguration(mc =>
            {
                mc.AddProfile(new AutoMapperProfiles());
            });
            IMapper mapper = mappingConfig.CreateMapper();
            services.AddSingleton(mapper);            
        }

这是user.cs模型文件

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;

namespace Schedular.API.Models
{
    public class User: IdentityUser<int>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        // to disable account and have a time limiter on when it can be enabled again

        public bool nonconcurrent { get; set; }

        // connect User table to the userRole join table. the below configure the relationship
        public virtual ICollection<UserRole> UserRoles {get; set;}
    }
}

下面是userForLoginDto

namespace Schedular.API.Dtos
{
    public class UserForLoginDto
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}

最佳答案

您可以通过多种方式实现这一目标。作为一个简单的起点,我将提供两种可能性。

首先可能是在您的数据(数据库、缓存、 token 等)中包含用户的 IP 地址。然后您可以验证是否正在使用第一个登录的 IP 地址,并阻止所有其他登录尝试或来自给定用户的任何其他 IP 的后续请求。

一个简单的方法如下所示:

public class UserForLoginDto
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string CurrentIpAddress { get; set; }
}

然后您可以从请求http 上下文 中提取 IP 并比较以验证登录,如下所示:

if(HttpContext.Connection.RemoteIpAddress == userForLoginDto.CurrentIpAddress)
{
    // do things here after validation.
}
else
{
    // kick-out invalid login attempt.
}

最后,您需要在用户结束 session 、因不活动而被踢出等时清除该 IP 值。因此,如果下一次登录是在不同的设备上,则可以更改 IP 地址。不是一个完美的解决方案,而是一个从简单开始的好方法。

这有利于概念验证,但不一定适合生产,因为您可能会遇到 IP 并不总是唯一的问题。


其次您可以生成一个 GUID、UUID 或其他某种唯一的数据,而不是 IP 地址,它并不总是唯一的通过 token 和持久化数据,即在数据库中。

public class UserForLoginDto
{
    public string Username { get; set; }
    public string Password { get; set; }
    public Guid CurrentLogin { get; set; }
}

然后继续在登录时进行与之前相同的检查:

if(Request.Headers.GetValues("your-token-here").FirstOrDefault() == userForLoginDto.CurrentLogin)
{
    // do things here after validation.
}
else
{
    // kick-out invalid login attempt.
}

所以这与第一个示例非常相似,但现在您有一个最有可能的唯一数据点来检查是否有更安全假设的请求。只需清除 session 结束时以及您在服务器端持久保存 token 数据的 token 数据,以便您可以在下次成功登录时重置它。

您还需要在每次成功登录时创建一个新的唯一数据点,如果已经存在一个数据点,您可以结束上一个 session 或使登录尝试无效,以帮助防止同时使用多个设备。


尝试研究 JWT(Json Web token )以寻找基于 token 的方法。那里有很多很好的例子和教程,如 JWT Authentication with C#creating and validating jwt tokens in asp net core .

同样,有很多方法可以实现像这样的复杂事物。所以尝试几种不同的方法,看看你喜欢什么,以及什么最适合你和你的项目或工作。就我个人而言,我建议研究基于 token 的身份验证和授权,这样您就可以更轻松地检查每个请求,但随着您深入研究,选择最适合您的场景的方法。

关于c# - 如何防止用户登录多个设备以及如何从之前的设备结束 session ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65117958/

相关文章:

c# - 方法的通用版本与接口(interface)版本

c# - ASP :ListBox Get Selected Items - One Liner?

javascript - 在 Javascript 中转换和读取 JSON 日期类型

c# - 是什么导致我的代码 : String was not recognized as a valid DateTime 出现此错误

c# - 返回 View (型号 : MyModel); equivalent in ASP.Net Core Razor Pages

c# - 按搜索条件检索文件列表的最快方法 - C#

c# - 如何将 SelectedNodeChanged 事件添加到 winform TreeView ?

.net - 在 IIS 7 中注册自定义模块

asp.net-mvc - 如何使用 Identity 在 ASP.NET Core 项目中禁用 HTTPS?

c# - 在 Docker 中使用 asp.net core 2 模拟 Windows 用户