optimise user context feature

master
Young 7 months ago
parent 7cc16c9d71
commit d14e6de1f2

@ -7,7 +7,6 @@ public static class TokenContextSetup
ArgumentNullException.ThrowIfNull(services);
services.TryAddSingleton<JwtSecurityTokenHandler>();
services.TryAddSingleton<DefaultTokenHandler>();
services.TryAddSingleton<ITokenBuilder, DefaultTokenBuilder>();
return services;
}
}

@ -1,11 +1,14 @@
using Infrastructure.HttpUserContext;
namespace Infrastructure.Extensions;
public static class UserContextSetup
{
public static IServiceCollection AddDefaultUserContext(this IServiceCollection services)
public static IServiceCollection AddDefaultUserContext<T>(this IServiceCollection services) where T : IEquatable<T>
{
ArgumentNullException.ThrowIfNull(services);
services.TryAddScoped(typeof(IUserContext<>), typeof(DefaultUserContext));
services.AddHttpContextAccessor();
services.TryAddScoped(typeof(IUserContext<T>), typeof(UserContext<T>));
return services;
}
}

@ -0,0 +1,22 @@
using System.Security.Claims;
namespace Infrastructure.HttpUserContext;
public interface IUserContext<TId> where TId : IEquatable<TId>
{
TId Id { get; }
string Username { get; }
string Name { get; }
string Email { get; }
string[] RoleIds { get; }
string RemoteIpAddress { get; }
JwtTokenInfo GenerateTokenInfo();
IList<Claim> GetClaimsFromUserContext();
}

@ -0,0 +1,69 @@
using System.Globalization;
using System.Security.Claims;
using Infrastructure.Utils;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
namespace Infrastructure.HttpUserContext;
public class UserContext<TId>(
IHttpContextAccessor httpContextAccessor,
JwtOptions jwtOptions,
IEncryptionService encryptionService,
JwtSecurityTokenHandler jwtSecurityTokenHandler)
: IUserContext<TId> where TId : IEquatable<TId>
{
private readonly ClaimsPrincipal principal = httpContextAccessor?.HttpContext?.User;
public TId Id => GetIdFromClaims();
public string Username => principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.UniqueName).Value;
public string Name => principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.Name).Value;
public string Email => principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.Email).Value;
public string[] RoleIds => principal.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray();
public string RemoteIpAddress => httpContextAccessor.HttpContext?.GetRequestIp()!;
public JwtTokenInfo GenerateTokenInfo()
{
var claims = GetClaimsFromUserContext();
var jwtToken = new JwtSecurityToken(
issuer: jwtOptions.Issuer,
audience: jwtOptions.Audience,
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddSeconds(jwtOptions.Expiration),
signingCredentials: jwtOptions.SigningCredentials);
var token = jwtSecurityTokenHandler.WriteToken(jwtToken);
token = encryptionService.Encrypt(token);
return new JwtTokenInfo(token, jwtOptions.Expiration,
JwtBearerDefaults.AuthenticationScheme);
}
public IList<Claim> GetClaimsFromUserContext()
{
var claims = new List<Claim>()
{
new(JwtRegisteredClaimNames.UniqueName, Username),
new(JwtRegisteredClaimNames.NameId, Id.ToString() ?? string.Empty),
new(JwtRegisteredClaimNames.Name, Name),
new(JwtRegisteredClaimNames.Email, Email),
new(JwtRegisteredClaimNames.Iat,
EpochTime.GetIntDate(DateTime.Now).ToString(CultureInfo.InvariantCulture),
ClaimValueTypes.Integer64),
new(JwtRegisteredClaimNames.Exp, jwtOptions.Expiration.ToString())
};
claims.AddRange(RoleIds.Select(rId => new Claim(ClaimTypes.Role, rId)));
return claims;
}
private TId GetIdFromClaims()
{
var idClaim = principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.NameId);
return (TId)Convert.ChangeType(idClaim.Value, typeof(TId));
}
}

@ -1,9 +0,0 @@
namespace Infrastructure.Security;
public class DefaultTokenBuilder(
JwtOptions jwtOptions,
JwtSecurityTokenHandler jwtSecurityTokenHandler,
IEncryptionService encryptionService)
: TokenBuilderBase<long>(jwtOptions, jwtSecurityTokenHandler, encryptionService)
{
}

@ -1,5 +0,0 @@
namespace Infrastructure.Security;
public class DefaultUserContext : UserContextBase<long>
{
}

@ -1,70 +0,0 @@
using System.Globalization;
using System.Security.Claims;
using Infrastructure.Utils;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
namespace Infrastructure.Security;
public interface ITokenBuilder
{
IList<Claim> GetClaimsFromUserContext<TId>(IUserContext<TId> userContext) where TId : IEquatable<TId>;
void SetUserContext(TokenValidatedContext context);
JwtTokenInfo GenerateJwtTokenInfo(IReadOnlyCollection<Claim> claims);
}
public abstract class TokenBuilderBase<TId>(
JwtOptions jwtOptions,
JwtSecurityTokenHandler jwtSecurityTokenHandler,
IEncryptionService encryptionService)
: ITokenBuilder where TId : IEquatable<TId>
{
public virtual IList<Claim> GetClaimsFromUserContext<TId>(IUserContext<TId> userContext) where TId : IEquatable<TId>
{
var claims = new List<Claim>()
{
new(JwtRegisteredClaimNames.UniqueName, userContext.Username),
new(JwtRegisteredClaimNames.NameId, userContext.Id.ToString() ?? string.Empty),
new(JwtRegisteredClaimNames.Name, userContext.Name),
new(JwtRegisteredClaimNames.Email, userContext.Email),
new(JwtRegisteredClaimNames.Iat,
EpochTime.GetIntDate(DateTime.Now).ToString(CultureInfo.InvariantCulture),
ClaimValueTypes.Integer64),
new(JwtRegisteredClaimNames.Exp, jwtOptions.Expiration.ToString())
};
claims.AddRange(userContext.RoleIds.Select(rId => new Claim(ClaimTypes.Role, rId)));
return claims;
}
public virtual void SetUserContext(TokenValidatedContext context)
{
var userContext =
context.HttpContext.RequestServices.GetService(typeof(IUserContext<TId>)) as IUserContext<TId> ??
throw new NullReferenceException(nameof(IUserContext<TId>));
var principal = context.Principal ?? throw new NullReferenceException(nameof(context.Principal));
var idClaim = principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.NameId);
userContext.Id = (TId)Convert.ChangeType(idClaim.Value, typeof(TId));
userContext.Username =
principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.UniqueName).Value;
userContext.Name = principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.Name).Value;
userContext.Email = principal.Claims.First(c => c.Type == JwtRegisteredClaimNames.Email).Value;
userContext.RoleIds = principal.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray();
userContext.RemoteIpAddress = context.HttpContext.GetRequestIp()!;
}
public virtual JwtTokenInfo GenerateJwtTokenInfo(IReadOnlyCollection<Claim> claims)
{
var jwtToken = new JwtSecurityToken(
issuer: jwtOptions.Issuer,
audience: jwtOptions.Audience,
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddSeconds(jwtOptions.Expiration),
signingCredentials: jwtOptions.SigningCredentials);
var token = jwtSecurityTokenHandler.WriteToken(jwtToken);
return new JwtTokenInfo(encryptionService.Encrypt(token), jwtOptions.Expiration,
JwtBearerDefaults.AuthenticationScheme);
}
}

@ -1,31 +0,0 @@
namespace Infrastructure.Security;
public interface IUserContext<TId> where TId : IEquatable<TId>
{
TId Id { get; set; }
string Username { get; set; }
string Name { get; set; }
string Email { get; set; }
string[] RoleIds { get; set; }
string RemoteIpAddress { get; set; }
}
public abstract class UserContextBase<TId> : IUserContext<TId> where TId : IEquatable<TId>
{
public TId Id { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string[] RoleIds { get; set; }
public string RemoteIpAddress { get; set; }
}

@ -3,17 +3,12 @@
namespace Infrastructure.Security;
public class JwtBearerOptionsPostConfigureOptions(
DefaultTokenHandler tokenHandler,
ITokenBuilder tokenBuilder)
DefaultTokenHandler tokenHandler)
: IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string? name, JwtBearerOptions options)
{
options.TokenHandlers.Clear();
options.TokenHandlers.Add(tokenHandler);
options.Events.OnTokenValidated = context =>
{
return Task.Run(() => { tokenBuilder.SetUserContext(context); });
};
}
}
Loading…
Cancel
Save