Compare commits

...

4 Commits

3 changed files with 149 additions and 1 deletions

View File

@ -0,0 +1,82 @@
using Microsoft.IdentityModel.Tokens;
using Mirea.Api.Security.Common.Interfaces;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
namespace Mirea.Api.Endpoint.Common.Services.Security;
public class JwtTokenService : IAccessToken
{
public required string Issuer { private get; init; }
public required string Audience { private get; init; }
public TimeSpan Lifetime { private get; init; }
public ReadOnlyMemory<byte> EncryptionKey { get; init; }
public ReadOnlyMemory<byte> SigningKey { private get; init; }
public (string Token, DateTime ExpireIn) GenerateToken(string userId)
{
var tokenHandler = new JwtSecurityTokenHandler();
var signingKey = new SymmetricSecurityKey(SigningKey.ToArray());
var encryptionKey = new SymmetricSecurityKey(EncryptionKey.ToArray());
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha512);
var expires = DateTime.UtcNow.Add(Lifetime);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = Issuer,
Audience = Audience,
Expires = expires,
SigningCredentials = signingCredentials,
Subject = new ClaimsIdentity(
[
new Claim(ClaimTypes.Name, userId),
// todo: get role by userId
new Claim(ClaimTypes.Role, "")
]),
EncryptingCredentials = new EncryptingCredentials(encryptionKey, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return (tokenHandler.WriteToken(token), expires);
}
public DateTimeOffset GetExpireDateTime(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var signingKey = new SymmetricSecurityKey(SigningKey.ToArray());
var encryptionKey = new SymmetricSecurityKey(EncryptionKey.ToArray());
var tokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Issuer,
ValidAudience = Audience,
IssuerSigningKey = signingKey,
TokenDecryptionKey = encryptionKey,
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = false
};
try
{
var claimsPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out _);
var expClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "exp");
if (expClaim != null && long.TryParse(expClaim.Value, out var expUnix))
return DateTimeOffset.FromUnixTimeSeconds(expUnix);
}
catch (SecurityTokenException)
{
return DateTimeOffset.MinValue;
}
return DateTimeOffset.MinValue;
}
}

View File

@ -23,9 +23,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Cronos" Version="0.8.4" /> <PackageReference Include="Cronos" Version="0.8.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
<PackageReference Include="StackExchange.Redis" Version="2.7.33" /> <PackageReference Include="StackExchange.Redis" Version="2.7.33" />
<PackageReference Include="Swashbuckle.AspNetCore.Versioning" Version="2.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore.Versioning" Version="2.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApiExplorer;
@ -6,20 +7,24 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Application;
using Mirea.Api.DataAccess.Persistence; using Mirea.Api.DataAccess.Persistence;
using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Interfaces;
using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Common.Services.Security;
using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Configuration;
using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General;
using Mirea.Api.Endpoint.Configuration.General.Validators; using Mirea.Api.Endpoint.Configuration.General.Validators;
using Mirea.Api.Endpoint.Configuration.Swagger; using Mirea.Api.Endpoint.Configuration.Swagger;
using Mirea.Api.Endpoint.Middleware; using Mirea.Api.Endpoint.Middleware;
using Mirea.Api.Security.Common.Interfaces;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
using System; using System;
using System.Collections; using System.Collections;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
namespace Mirea.Api.Endpoint; namespace Mirea.Api.Endpoint;
@ -40,6 +45,66 @@ public class Program
return result.Build(); return result.Build();
} }
private static IServiceCollection ConfigureJwtToken(IServiceCollection services, IConfiguration configuration)
{
var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!));
var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty);
if (jwtDecrypt.Length != 32)
throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length);
var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty);
if (jwtKey.Length != 64)
throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length);
var jwtIssuer = configuration["SECURITY_JWT_ISSUER"];
var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"];
if (string.IsNullOrEmpty(jwtAudience) || string.IsNullOrEmpty(jwtIssuer))
throw new InvalidOperationException("The \"SECURITY_JWT_ISSUER\" and \"SECURITY_JWT_AUDIENCE\" are not specified");
services.AddSingleton<IAccessToken, JwtTokenService>(_ => new JwtTokenService
{
Audience = jwtAudience,
Issuer = jwtIssuer,
Lifetime = lifeTimeJwt,
EncryptionKey = jwtDecrypt,
SigningKey = jwtKey
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtIssuer,
ValidateAudience = true,
ValidAudience = jwtAudience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt)
};
});
return services;
}
private static IServiceCollection ConfigureSecurity(IServiceCollection services)
{
services.AddSingleton<IAccessToken, JwtTokenService>();
services.AddSingleton<IRevokedToken, MemoryRevokedTokenService>();
return services;
}
public static void Main(string[] args) public static void Main(string[] args)
{ {
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);