Persist user token
parent
36d23aa924
commit
19915f05d8
|
@ -1,5 +1,6 @@
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using IdentityServer.Application;
|
using IdentityServer.Application;
|
||||||
|
using IdentityServer.Application.Services.Abstractions;
|
||||||
using IdentityServer.Domain.Data;
|
using IdentityServer.Domain.Data;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using MediatR.Pipeline;
|
using MediatR.Pipeline;
|
||||||
|
@ -76,6 +77,9 @@ namespace IdentityServer.Api
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
});
|
});
|
||||||
app.ConfigureSwagger("IdentityServer API");
|
app.ConfigureSwagger("IdentityServer API");
|
||||||
|
|
||||||
|
var behaviorService = app.ApplicationServices.GetService<IBehaviorService>();
|
||||||
|
behaviorService.FillTokenStore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,11 @@
|
||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"Restrictions": {
|
||||||
|
"MaxFailedLoginAttempts": 5
|
||||||
|
},
|
||||||
|
"Token": {
|
||||||
|
"ValidityInMinutes": 43800
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using IdentityServer.Application.Services;
|
using IdentityServer.Application.Services;
|
||||||
|
using IdentityServer.Application.Services.Abstractions;
|
||||||
using IdentityServer.Application.Stores;
|
using IdentityServer.Application.Stores;
|
||||||
|
using IdentityServer.Domain.Abstractions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace IdentityServer.Application
|
namespace IdentityServer.Application
|
||||||
|
@ -9,13 +11,15 @@ namespace IdentityServer.Application
|
||||||
public static void AddApplicationServices(this IServiceCollection services)
|
public static void AddApplicationServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddStores();
|
services.AddStores();
|
||||||
|
services.AddSingleton<IConfigProvider, ConfigProvider>();
|
||||||
services.AddSingleton<ITokenService, TokenService>();
|
services.AddSingleton<ITokenService, TokenService>();
|
||||||
services.AddScoped<IUserService, UserService>();
|
services.AddScoped<IUserService, UserService>();
|
||||||
|
services.AddScoped<IBehaviorService, BehaviorService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddStores(this IServiceCollection services)
|
private static void AddStores(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<ISecurityStore, SecurityStore>();
|
services.AddSingleton<ITokenStore, TokenStore>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace IdentityServer.Application.Services.Abstractions
|
||||||
|
{
|
||||||
|
public interface IBehaviorService
|
||||||
|
{
|
||||||
|
void FillTokenStore();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using IdentityServer.Application.Services.Abstractions;
|
||||||
|
using IdentityServer.Application.Stores;
|
||||||
|
using IdentityServer.Domain.Models;
|
||||||
|
using IdentityServer.Domain.Repositories;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IdentityServer.Application.Services
|
||||||
|
{
|
||||||
|
internal class BehaviorService : IBehaviorService
|
||||||
|
{
|
||||||
|
private readonly ITokenStore _securityStore;
|
||||||
|
private readonly IIdentityRepository _identityRepository;
|
||||||
|
|
||||||
|
public BehaviorService(ITokenStore securityStore, IIdentityRepository identityRepository)
|
||||||
|
{
|
||||||
|
_securityStore = securityStore;
|
||||||
|
_identityRepository = identityRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FillTokenStore()
|
||||||
|
=> FillTokenStoreAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
public async Task FillTokenStoreAsync()
|
||||||
|
{
|
||||||
|
var activeTokens = await _identityRepository.GetActiveTokens();
|
||||||
|
if (activeTokens.Length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var token in activeTokens)
|
||||||
|
{
|
||||||
|
var storeToken = new Token() { Raw = token.Token, ValidFrom = token.ValidFrom, ValidUntil = token.ValidUntil };
|
||||||
|
_securityStore.SetToken(storeToken, token.UserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using IdentityServer.Domain.Abstractions;
|
||||||
|
using IdentityServer.Domain.Models.Config;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace IdentityServer.Application.Services
|
||||||
|
{
|
||||||
|
internal class ConfigProvider : IConfigProvider
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public ConfigProvider(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration=configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestrictionsConfig Restrictions => _configuration.GetSection("Restrictions").Get<RestrictionsConfig>();
|
||||||
|
public TokenConfig Token => _configuration.GetSection("Token").Get<TokenConfig>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ namespace IdentityServer.Application.Services
|
||||||
{
|
{
|
||||||
internal interface ITokenService
|
internal interface ITokenService
|
||||||
{
|
{
|
||||||
string GenerateTokenRaw(AppUser user);
|
Token GenerateToken(AppUser user);
|
||||||
TokenCore ExtractTokenCore(string tokenRaw);
|
TokenCore ExtractTokenCore(string tokenRaw);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using IdentityServer.Domain.Abstractions;
|
||||||
using IdentityServer.Domain.Entities;
|
using IdentityServer.Domain.Entities;
|
||||||
using IdentityServer.Domain.Models;
|
using IdentityServer.Domain.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@ -11,22 +12,32 @@ namespace IdentityServer.Application.Services
|
||||||
internal class TokenService : ITokenService
|
internal class TokenService : ITokenService
|
||||||
{
|
{
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
private readonly IConfigProvider _configProvider;
|
||||||
|
|
||||||
public TokenService(IMapper mapper)
|
public TokenService(IMapper mapper, IConfigProvider configProvider)
|
||||||
{
|
{
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_configProvider = configProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateTokenRaw(AppUser user)
|
public Token GenerateToken(AppUser user)
|
||||||
{
|
{
|
||||||
var tokenCore = GenerateToken(user);
|
var tokenRaw = GenerateTokenRaw(user);
|
||||||
|
var currentDate = DateTime.Now;
|
||||||
|
var token = new Token() { Raw = tokenRaw, ValidFrom = currentDate, ValidUntil = currentDate.AddMinutes(_configProvider.Token.ValidityInMinutes) };
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateTokenRaw(AppUser user)
|
||||||
|
{
|
||||||
|
var tokenCore = GenerateTokenCore(user);
|
||||||
var tokenCoreString = JsonConvert.SerializeObject(tokenCore);
|
var tokenCoreString = JsonConvert.SerializeObject(tokenCore);
|
||||||
var tokenCoreBytes = Encoding.UTF8.GetBytes(tokenCoreString);
|
var tokenCoreBytes = Encoding.UTF8.GetBytes(tokenCoreString);
|
||||||
var tokenRaw = Convert.ToBase64String(tokenCoreBytes);
|
var tokenRaw = Convert.ToBase64String(tokenCoreBytes);
|
||||||
return tokenRaw;
|
return tokenRaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenCore GenerateToken(AppUser user)
|
private TokenCore GenerateTokenCore(AppUser user)
|
||||||
{
|
{
|
||||||
var tokenCore = _mapper.Map<TokenCore>(user);
|
var tokenCore = _mapper.Map<TokenCore>(user);
|
||||||
tokenCore.LockStamp = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");
|
tokenCore.LockStamp = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using IdentityServer.Application.Stores;
|
using IdentityServer.Application.Stores;
|
||||||
|
using IdentityServer.Domain.Abstractions;
|
||||||
|
using IdentityServer.Domain.Entities;
|
||||||
using IdentityServer.Domain.Models;
|
using IdentityServer.Domain.Models;
|
||||||
using IdentityServer.Domain.Repositories;
|
using IdentityServer.Domain.Repositories;
|
||||||
using System;
|
using System;
|
||||||
|
@ -8,26 +10,27 @@ namespace IdentityServer.Application.Services
|
||||||
{
|
{
|
||||||
internal class UserService : IUserService
|
internal class UserService : IUserService
|
||||||
{
|
{
|
||||||
private readonly ISecurityStore _securityStore;
|
private readonly ITokenStore _securityStore;
|
||||||
private readonly IIdentityRepository _identityRepository;
|
private readonly IIdentityRepository _identityRepository;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
|
private readonly IConfigProvider _configProvider;
|
||||||
|
|
||||||
public UserService(ISecurityStore securityStore, IIdentityRepository identityRepository, ITokenService tokenService)
|
public UserService(ITokenStore securityStore, IIdentityRepository identityRepository, ITokenService tokenService, IConfigProvider configProvider)
|
||||||
{
|
{
|
||||||
_securityStore = securityStore;
|
_securityStore = securityStore;
|
||||||
_identityRepository = identityRepository;
|
_identityRepository = identityRepository;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
|
_configProvider = configProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Token> Authenticate(string userName, string password)
|
public async Task<Token> Authenticate(string userName, string password)
|
||||||
{
|
{
|
||||||
var user = await _identityRepository.GetUser(userName, password);
|
var user = await _identityRepository.GetUser(userName, password);
|
||||||
if (user == null)
|
var valid = ValidateUser(user);
|
||||||
|
if (!valid)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var tokenRaw = _tokenService.GenerateTokenRaw(user);
|
var token = _tokenService.GenerateToken(user);
|
||||||
var currentDate = DateTime.Now;
|
|
||||||
var token = new Token() { Raw = tokenRaw, ValidFrom = currentDate, ValidUntil = currentDate.AddMonths(12) };
|
|
||||||
_securityStore.SetToken(token, user.UserId);
|
_securityStore.SetToken(token, user.UserId);
|
||||||
await _identityRepository.UpdateUserAfterAuthentication(user, token);
|
await _identityRepository.UpdateUserAfterAuthentication(user, token);
|
||||||
|
|
||||||
|
@ -42,5 +45,16 @@ namespace IdentityServer.Application.Services
|
||||||
|
|
||||||
return tokenCore;
|
return tokenCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ValidateUser(AppUser user)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (user.FailedLoginAttempts.HasValue && user.FailedLoginAttempts.Value > _configProvider.Restrictions.MaxFailedLoginAttempts)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace IdentityServer.Application.Stores
|
namespace IdentityServer.Application.Stores
|
||||||
{
|
{
|
||||||
internal interface ISecurityStore
|
internal interface ITokenStore
|
||||||
{
|
{
|
||||||
void SetToken(Token token, int userId);
|
void SetToken(Token token, int userId);
|
||||||
TokenCore ValidateAndGetTokenCore(string token);
|
TokenCore ValidateAndGetTokenCore(string token);
|
|
@ -6,12 +6,12 @@ using System.Linq;
|
||||||
|
|
||||||
namespace IdentityServer.Application.Stores
|
namespace IdentityServer.Application.Stores
|
||||||
{
|
{
|
||||||
internal class SecurityStore : ISecurityStore
|
internal class TokenStore : ITokenStore
|
||||||
{
|
{
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private ConcurrentDictionary<int, List<Token>> Tokens { get; }
|
private ConcurrentDictionary<int, List<Token>> Tokens { get; }
|
||||||
|
|
||||||
public SecurityStore(ITokenService tokenService)
|
public TokenStore(ITokenService tokenService)
|
||||||
{
|
{
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
Tokens = new ConcurrentDictionary<int, List<Token>>();
|
Tokens = new ConcurrentDictionary<int, List<Token>>();
|
|
@ -8,8 +8,8 @@ namespace IdentityServer.Domain.Data.EntityTypeConfiguration
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<UserToken> builder)
|
public void Configure(EntityTypeBuilder<UserToken> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("UserToken").HasKey(z => z.Id);
|
builder.ToTable("UserToken").HasKey(z => z.TokenId);
|
||||||
builder.Property(z => z.Id).ValueGeneratedOnAdd();
|
builder.Property(z => z.TokenId).ValueGeneratedOnAdd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using IdentityServer.Domain.Models;
|
||||||
using IdentityServer.Domain.Repositories;
|
using IdentityServer.Domain.Repositories;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace IdentityServer.Domain.Data.Repositories
|
namespace IdentityServer.Domain.Data.Repositories
|
||||||
|
@ -41,5 +42,13 @@ namespace IdentityServer.Domain.Data.Repositories
|
||||||
|
|
||||||
await _dbContext.SaveChangesAsync();
|
await _dbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<UserToken[]> GetActiveTokens()
|
||||||
|
{
|
||||||
|
var currentDate = DateTime.Now;
|
||||||
|
var query = _dbContext.UserTokens
|
||||||
|
.Where(z => z.ValidFrom <= currentDate && z.ValidUntil >= currentDate && (!z.Burnt.HasValue || z.Burnt.Value == false));
|
||||||
|
return query.ToArrayAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ if not exists (select top 1 1 from sys.objects where name = 'UserToken' and type
|
||||||
begin
|
begin
|
||||||
create table UserToken
|
create table UserToken
|
||||||
(
|
(
|
||||||
Id int identity(1, 1) constraint PK_Token primary key,
|
TokenId int identity(1, 1) constraint PK_Token primary key,
|
||||||
UserId int not null constraint FK_Token_AppUser foreign key references AppUser(UserId),
|
UserId int not null constraint FK_Token_AppUser foreign key references AppUser(UserId),
|
||||||
Token varchar(1000) not null,
|
Token varchar(1000) not null,
|
||||||
ValidFrom datetime not null,
|
ValidFrom datetime not null,
|
||||||
ValidUntil datetime not null
|
ValidUntil datetime not null,
|
||||||
|
Burnt bit
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
go
|
go
|
|
@ -0,0 +1,10 @@
|
||||||
|
using IdentityServer.Domain.Models.Config;
|
||||||
|
|
||||||
|
namespace IdentityServer.Domain.Abstractions
|
||||||
|
{
|
||||||
|
public interface IConfigProvider
|
||||||
|
{
|
||||||
|
RestrictionsConfig Restrictions { get; }
|
||||||
|
TokenConfig Token { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,11 @@ namespace IdentityServer.Domain.Entities
|
||||||
{
|
{
|
||||||
public class UserToken
|
public class UserToken
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int TokenId { get; set; }
|
||||||
public int UserId { get; set; }
|
public int UserId { get; set; }
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
public DateTime ValidFrom { get; set; }
|
public DateTime ValidFrom { get; set; }
|
||||||
public DateTime ValidUntil { get; set; }
|
public DateTime ValidUntil { get; set; }
|
||||||
|
public bool? Burnt { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace IdentityServer.Domain.Models.Config
|
||||||
|
{
|
||||||
|
public class RestrictionsConfig
|
||||||
|
{
|
||||||
|
public int MaxFailedLoginAttempts { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace IdentityServer.Domain.Models.Config
|
||||||
|
{
|
||||||
|
public class TokenConfig
|
||||||
|
{
|
||||||
|
public int ValidityInMinutes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,5 +8,6 @@ namespace IdentityServer.Domain.Repositories
|
||||||
{
|
{
|
||||||
Task<AppUser> GetUser(string userName, string password);
|
Task<AppUser> GetUser(string userName, string password);
|
||||||
Task UpdateUserAfterAuthentication(AppUser user, Token token);
|
Task UpdateUserAfterAuthentication(AppUser user, Token token);
|
||||||
|
Task<UserToken[]> GetActiveTokens();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
◾ Token improvements and hard changes
|
◾ Token improvements and hard changes
|
||||||
◾ Increase user information complexity
|
◾ Increase user information complexity
|
||||||
◾ New token structure and generation mechanism
|
◾ New token structure and generation mechanism
|
||||||
|
◾ Persist tokens and reload active ones at server restart
|
||||||
</Content>
|
</Content>
|
||||||
</Note>
|
</Note>
|
||||||
</ReleaseNotes>
|
</ReleaseNotes>
|
Loading…
Reference in New Issue