Я создал репозиторий github с изменениями по умолчанию .net core 2.1 шаблона, необходимого для разрешения только одного сеанса. https://github.com/xKloc/IdentityWithSession
Вот суть.
Сначала переопределите класс UserClaimsPrincipalFactory<IdentityUser>
по умолчанию на собственный, который добавит ваш сеанс к заявкам пользователей. Заявки - это просто пара ключ / значение, которая будет сохранена в файле cookie пользователя, а также на сервере в таблице AspNetUserClaims
.
Добавьте этот класс в любом месте вашего проекта.
public class ApplicationClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser>
{
private readonly UserManager<IdentityUser> _userManager;
public ApplicationClaimsPrincipalFactory(UserManager<IdentityUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor)
{
_userManager = userManager;
}
public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
{
// find old sessions and remove
var claims = await _userManager.GetClaimsAsync(user);
var session = claims.Where(e => e.Type == "session");
await _userManager.RemoveClaimsAsync(user, session);
// add new session claim
await _userManager.AddClaimAsync(user, new Claim("session", Guid.NewGuid().ToString()));
// create principal
var principal = await base.CreateAsync(user);
return principal;
}
}
Далее мы создадим обработчик авторизации, который будет проверять, что сеанс действителен для каждого запроса.
Обработчик сопоставит утверждение сеанса из файла cookie пользователя с утверждением сеанса, хранящимся в базе данных. Если они совпадают, пользователь имеет право продолжить. Если они не совпадают, пользователь получит сообщение «Отказано в доступе».
Добавьте эти два класса в любом месте вашего проекта.
public class ValidSessionRequirement : IAuthorizationRequirement
{
}
public class ValidSessionHandler : AuthorizationHandler<ValidSessionRequirement>
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public ValidSessionHandler(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidSessionRequirement requirement)
{
// if the user isn't authenticated then no need to check session
if (!context.User.Identity.IsAuthenticated)
return;
// get the user and session claim
var user = await _userManager.GetUserAsync(context.User);
var claims = await _userManager.GetClaimsAsync(user);
var serverSession = claims.First(e => e.Type == "session");
var clientSession = context.User.FindFirst("session");
// if the client session matches the server session then the user is authorized
if (serverSession?.Value == clientSession?.Value)
{
context.Succeed(requirement);
}
return;
}
}
Наконец, просто зарегистрируйте эти новые классы при запуске, чтобы они вызывались.
Добавьте этот код в ваш Startup
класс по методу ConfigureServices
, прямо под services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
// build default authorization policy
var defaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new ValidSessionRequirement())
.Build();
// add authorization to the pipe
services.AddAuthorization(options =>
{
options.DefaultPolicy = defaultPolicy;
});
// register new claims factory
services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, ApplicationClaimsPrincipalFactory>();
// register valid session handler
services.AddTransient<IAuthorizationHandler, ValidSessionHandler>();