#c# #asp.net #asp.net-core #asp.net-identity
#c# #asp.net #asp.net-core #asp.net-identity
Вопрос:
Как мне запретить вход с идентификатором пользователя только на один компьютер одновременно? Например, пользователь «Sam» входит в систему на своем рабочем столе. Пользователь «Sam» также входит в систему со своего ноутбука. Теперь мне нужно, чтобы сеанс на рабочем столе был уничтожен, или я хочу, чтобы старый токен был недействительным на рабочем столе.
Я использую dotnet 5.0
Вам нужен столбец с именем currentlyloggedinUser в таблице «пользователи» в базе данных, чтобы узнать, вошли ли они в систему с помощью Boolean?
Вам также понадобится печать безопасности?
Ниже приведен метод входа в систему, который помещается в 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; }
}
}
Комментарии:
1. Зачем вам нужно было запретить одновременный вход пользователя в систему?
Ответ №1:
Есть несколько способов, которыми вы могли бы добиться этого. Для простой отправной точки я предоставлю две возможности.
Первым может быть включение IP-адреса пользователя в ваши данные (база данных, кэш, токен и т. Д.). Затем вы можете проверить, используется ли IP-адрес первого входа в систему, и заблокировать все другие попытки входа или последующие запросы с любых других IP-адресов для данного пользователя.
Простой способ сделать это может выглядеть примерно так, как показано ниже:
public class UserForLoginDto
{
public string Username { get; set; }
public string Password { get; set; }
public string CurrentIpAddress { get; set; }
}
Затем вы можете извлечь IP-адрес из контекста запроса или http и сравнить для проверки при входе в систему следующим образом:
if(HttpContext.Connection.RemoteIpAddress == userForLoginDto.CurrentIpAddress)
{
// do things here after validation.
}
else
{
// kick-out invalid login attempt.
}
Наконец, вы захотите очистить это значение IP, когда пользователь завершает сеанс, получает удар от бездействия и т. Д. Таким образом, следующий вход в систему может изменить IP-адрес, если он находится на другом устройстве. Не идеальное решение, но хороший способ начать с простого.
Это было бы хорошо для проверки концепции, но не обязательно идеально для производства, поскольку вы можете столкнуться с проблемами, когда IP-адреса не всегда уникальны.
Во-вторых, вместо IP-адреса, который не всегда уникален, вы могли бы вместо этого сгенерировать GUID, UUID или какие-либо другие уникальные данные, которые передаются через токены и сохраняются данные, т.Е. в базе данных.
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.
}
Итак, это довольно похоже на первый пример, но теперь у вас есть, скорее всего, уникальная точка данных для проверки запросов на более безопасные предположения. Просто очистите данные токена в конце сеанса и там, где вы сохраняете его на стороне сервера, чтобы вы могли сбросить его при следующем успешном входе в систему.
Вы также захотите создавать новую уникальную точку данных при каждом успешном входе в систему, и если она уже существует, вы можете либо завершить предыдущий сеанс, либо аннулировать попытку входа в систему, чтобы предотвратить одновременное использование нескольких устройств.
Попробуйте изучить JWT (веб-токены Json) для подходов, основанных на токенах. Существует множество отличных примеров и руководств, таких как аутентификация JWT с помощью C # или создание и проверка токенов jwt в asp net core.
Опять же, есть много способов реализовать сложные вещи, подобные этому. Итак, попробуйте несколько разных подходов, посмотрите, что вам нравится, и что лучше всего подходит для вас и вашего проекта или работы. Лично я бы посоветовал изучить аутентификацию и авторизацию на основе токенов, чтобы вы могли легче проверять каждый запрос, но по мере углубления в него выбирайте то, что лучше всего работает в вашем сценарии.
Комментарии:
1. Это может привести к сбою, если пользователь использует один и тот же Wi-Fi (т. Е. Маршрутизатор шлюза с одним NAT-адресом) со своего ноутбука и мобильного телефона. Что может работать лучше, так это установить некоторый случайный токен и использовать его вместо IP-адреса.
2. Это также приведет к сбою, если сайт размещен в ферме серверов (много экземпляров одного и того же сайта)
3. @DarcyThomas отличный момент, как я уже сказал, он не идеален, но может быть полезен для poc в сжатые сроки. Обновленный ответ, чтобы лучше отразить это.
Ответ №2:
Перед созданием и возвратом токена сеанса получите текущий активный токен сеанса для пользователя, сделайте его недействительным, а затем верните новый. С помощью этого мы можем убедиться, что в данный момент времени существует только один допустимый сеанс.