feat: jwt application

This commit is contained in:
2024-10-13 14:56:22 +07:00
parent a934952816
commit e9f32fc32e
11 changed files with 295 additions and 4 deletions

View File

@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jwt.Server", "Jwt.Server\Jw
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RsaKeyLoader", "RsaKeyLoader\RsaKeyLoader.csproj", "{579C8783-7E95-40F3-96F4-BEBFB40F2D38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jwt.Application", "Jwt.Application\Jwt.Application.csproj", "{E1DC6422-489C-424C-BC3A-3844599B43C9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -40,11 +42,16 @@ Global
{579C8783-7E95-40F3-96F4-BEBFB40F2D38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{579C8783-7E95-40F3-96F4-BEBFB40F2D38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{579C8783-7E95-40F3-96F4-BEBFB40F2D38}.Release|Any CPU.Build.0 = Release|Any CPU
{E1DC6422-489C-424C-BC3A-3844599B43C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1DC6422-489C-424C-BC3A-3844599B43C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1DC6422-489C-424C-BC3A-3844599B43C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1DC6422-489C-424C-BC3A-3844599B43C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3684B6DC-9301-496C-9832-FFC26084C496} = {A7B682CD-CF61-4684-977B-82E48A4051D8}
{63CE5DC9-F976-430B-8B12-50FB3DA3F872} = {A7B682CD-CF61-4684-977B-82E48A4051D8}
{BAD4810F-B60A-42F1-8176-649855B93794} = {29AFF1AF-FE18-491A-AFE1-CE2786187166}
{579C8783-7E95-40F3-96F4-BEBFB40F2D38} = {29AFF1AF-FE18-491A-AFE1-CE2786187166}
{E1DC6422-489C-424C-BC3A-3844599B43C9} = {29AFF1AF-FE18-491A-AFE1-CE2786187166}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6"/>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.1.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RsaKeyLoader\RsaKeyLoader.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@Jwt.Application_HostAddress = http://localhost:5064
GET {{Jwt.Application_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,76 @@
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
var jwKey = await new HttpClient().GetStringAsync("https://localhost:5000/jwk");
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddCors();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication()
.AddJwtBearer(b =>
{
b.TokenValidationParameters = new TokenValidationParameters()
{
// для облегчения дебага
ValidateAudience = false,
ValidateIssuer = false,
};
// important
b.Configuration = new OpenIdConnectConfiguration()
{
SigningKeys =
{
JsonWebKey.Create(jwKey)
}
};
// b.Events = new JwtBearerEvents()
// {
// OnMessageReceived = (ctx) =>
// {
// if (ctx.Request.Query.ContainsKey("token"))
// {
// ctx.Token = ctx.Request.Query["token"];
// }
//
// return Task.CompletedTask;
// }
// };
});
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors(p =>
{
p.AllowAnyOrigin();
p.AllowAnyMethod();
p.AllowAnyHeader();
});
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/me", (HttpContext ctx) =>
{
return ctx.User.FindFirst("name").Value;
})
.RequireAuthorization()
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:2141",
"sslPort": 44353
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:7001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7000;http://localhost:7001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -7,8 +7,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RsaKeyLoader\RsaKeyLoader.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,3 +1,19 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using RsaKeyLoader;
var _users = new List<(string Name, string Id)>()
{
new ValueTuple<string, string>("Danil", Guid.NewGuid().ToString())
};
var _rsaKey = KeyLoader.GetGeneratedKey();
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@@ -5,6 +21,42 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// todo: package Microsoft.AspNetCore.Authentication.JwtBearer;
builder.Services.AddAuthentication()
.AddJwtBearer(b =>
{
b.TokenValidationParameters = new TokenValidationParameters()
{
// для облегчения дебага
ValidateAudience = false,
ValidateIssuer = false,
};
// important
b.Configuration = new OpenIdConnectConfiguration()
{
SigningKeys =
{
new RsaSecurityKey(_rsaKey)
}
};
// b.Events = new JwtBearerEvents()
// {
// OnMessageReceived = (ctx) =>
// {
// if (ctx.Request.Query.ContainsKey("token"))
// {
// ctx.Token = ctx.Request.Query["token"];
// }
//
// return Task.CompletedTask;
// }
// };
});
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -15,6 +67,8 @@ if (app.Environment.IsDevelopment())
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
var summaries = new[]
{
@@ -33,12 +87,55 @@ app.MapGet("/weatherforecast", () =>
.ToArray();
return forecast;
})
.RequireAuthorization()
.WithName("GetWeatherForecast")
.WithOpenApi();
app.MapGet("/login", (string userName) =>
{
var user = _users.First(x => x.Name == userName);
var handler = new JwtSecurityTokenHandler();
var key = new RsaSecurityKey(_rsaKey);
var token = handler.CreateToken(new SecurityTokenDescriptor()
{
Issuer = "https://localhost:5000",
Subject = new ClaimsIdentity(new List<Claim>()
{
new Claim("sub", user.Id),
new Claim("name", user.Name)
}),
// important
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256)
});
return handler.WriteToken(token);
});
app.MapGet("/jwk", () =>
{
var publicKey = RSA.Create();
publicKey.ImportRSAPublicKey(_rsaKey.ExportRSAPublicKey(), out _);
var key = new RsaSecurityKey(publicKey);
return JsonWebKeyConverter.ConvertFromRSASecurityKey(key);
});
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
/* script:
const token = '';
fetch("/weatherforecast", {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + token
}
}).then(r => r.json()).then(r => console.log(r));
*/

View File

@@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5079",
"applicationUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -24,7 +24,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7256;http://localhost:5079",
"applicationUrl": "https://localhost:5000;http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@@ -4,10 +4,32 @@ namespace RsaKeyLoader;
public class KeyLoader
{
public void Generate(string path)
public static string PathString =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "auth-key");
public static RSA GetGeneratedKey()
{
if (File.Exists(PathString))
{
return LoadKey(PathString);
}
else
{
Generate(PathString);
return LoadKey(PathString);
}
}
public static void Generate(string path)
{
var rsaKey = RSA.Create();
var privateKey = rsaKey.ExportRSAPrivateKey();
File.WriteAllBytes(path, privateKey);
}
public static RSA LoadKey(string path)
{
var rsaKey = RSA.Create();
rsaKey.ImportRSAPrivateKey(File.ReadAllBytes(path), out _);
return rsaKey;
}
}