289 lines
10 KiB
C#
289 lines
10 KiB
C#
using FluentValidation; // <--- 关键!这是现在唯一需要的 using
|
||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.IdentityModel.Tokens;
|
||
using SimpleTodoApiWithPg.Data;
|
||
using SimpleTodoApiWithPg.Endpoints;
|
||
using SimpleTodoApiWithPg.UserService;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
|
||
var builder = WebApplication.CreateBuilder(args);
|
||
|
||
builder.Logging.AddConsole(); //日志服务
|
||
builder.Services.AddScoped<UserServices>(); //注册用户服务
|
||
// 1. 添加OpenAPI/Swagger生成器服务到容器
|
||
builder.Services.AddEndpointsApiExplorer(); // 这个服务负责发现和描述API端点
|
||
builder.Services.AddSwaggerGen(); // 这个服务负责根据端点描述生成OpenAPI JSON文档
|
||
|
||
// 1. 扫描并注册你项目里的所有验证器
|
||
builder.Services.AddValidatorsFromAssemblyContaining<Program>(includeInternalTypes: true);
|
||
// 注册所有 validator(按包含类型或指定程序集)
|
||
|
||
|
||
|
||
|
||
|
||
var certPath = builder.Configuration["Jwt:CertificatePath"];
|
||
var certPassword = builder.Configuration["Jwt:CertificatePassword"];
|
||
var certificate = X509CertificateLoader.LoadPkcs12FromFile(certPath!, certPassword);
|
||
|
||
builder.Services.AddAuthentication(options =>
|
||
{
|
||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||
})
|
||
.AddJwtBearer(options =>
|
||
{
|
||
options.TokenValidationParameters = new TokenValidationParameters
|
||
{
|
||
ValidateIssuer = true,
|
||
ValidateAudience = true,
|
||
ValidateLifetime = true,
|
||
ValidateIssuerSigningKey = true,
|
||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
||
// 使用证书的公钥进行验证
|
||
IssuerSigningKey = new ECDsaSecurityKey(certificate.GetECDsaPublicKey()!),
|
||
// 确保指定正确的签名算法
|
||
// 对于 ECDSA P-256,应该是 "ES256"
|
||
ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256] // 可选,增加安全性
|
||
};
|
||
|
||
// 新增:配置认证事件
|
||
options.Events = new JwtBearerEvents
|
||
{
|
||
// 当认证失败,需要向客户端发起质询(Challenge)时触发
|
||
OnChallenge = context =>
|
||
{
|
||
// 阻止默认的响应行为,我们将来完全自定义响应
|
||
context.HandleResponse();
|
||
|
||
context.Response.StatusCode = 401;
|
||
context.Response.ContentType = "application/json";
|
||
|
||
// 默认的错误信息
|
||
var message = "身份验证失败,请检查您的 Token。";
|
||
|
||
// 检查具体的失败原因,如果是 Token 过期,就给出更明确的提示
|
||
// AuthenticateFailure 属性包含了认证失败时的异常信息
|
||
if (context.AuthenticateFailure is SecurityTokenExpiredException)
|
||
{
|
||
message = "您的 Token 已过期,请重新登录或刷新 Token。";
|
||
}
|
||
|
||
// 您也可以在这里记录日志,方便排查问题
|
||
// logger.LogError(context.AuthenticateFailure, "An authentication error occurred.");
|
||
|
||
var response = new
|
||
{
|
||
success = false,
|
||
message = message
|
||
};
|
||
|
||
// 将自定义的响应内容写入 Response Body
|
||
return context.Response.WriteAsJsonAsync(response);
|
||
}
|
||
};
|
||
|
||
});
|
||
|
||
builder.Services.AddAuthorization();
|
||
|
||
|
||
// === 配置 CORS ===
|
||
// 定义一个 CORS 策略名称
|
||
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||
|
||
// 添加 CORS 服务到依赖注入容器
|
||
builder.Services.AddCors(options =>
|
||
{
|
||
options.AddPolicy(name: MyAllowSpecificOrigins,
|
||
policy =>
|
||
{
|
||
// 允许所有来源进行访问。
|
||
// 注意:这在生产环境中是极度不安全的,仅用于本地开发和测试。
|
||
// 在生产环境中,您应该明确指定允许的源:
|
||
// policy.WithOrigins("http://localhost:8080", "https://yourfrontend.com")
|
||
// 或者,如果您直接打开HTML文件,其来源是'null',您可以明确允许它:
|
||
// policy.WithOrigins("null") // 在此测试场景下,这是一个选项,但AllowAnyOrigin更通用
|
||
|
||
policy.AllowAnyOrigin() // 允许任何来源的请求
|
||
.AllowAnyHeader() // 允许任何请求头
|
||
.AllowAnyMethod(); // 允许任何 HTTP 方法 (GET, POST, PUT, DELETE)
|
||
});
|
||
});
|
||
|
||
|
||
// === 配置 EF Core 连接 PostgreSQL ===
|
||
// 从配置中获取连接字符串
|
||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||
|
||
// 添加数据库上下文服务
|
||
builder.Services.AddDbContext<AppDbContext>(options =>
|
||
options.UseNpgsql(connectionString)); // 使用 Npgsql 作为 PostgreSQL 提供者
|
||
|
||
|
||
var app = builder.Build();
|
||
|
||
// 2. 在管道中启用中间件 (通常只在开发环境中启用)
|
||
if (app.Environment.IsDevelopment())
|
||
{
|
||
app.UseSwagger(); // 这个中间件负责提供生成的 swagger.json 文件
|
||
app.UseSwaggerUI(); // 这个中间件负责提供交互式的Swagger UI界面
|
||
}
|
||
// === CORS 中间件必须放在其他中间件之前 (例如路由,授权,认证等) ===
|
||
// 启用 CORS
|
||
app.UseCors(MyAllowSpecificOrigins);
|
||
|
||
// === 应用数据库迁移 (仅在开发环境中推荐) ===
|
||
// 在应用启动时自动应用所有挂起的数据库迁移
|
||
using (var scope = app.Services.CreateScope())
|
||
{
|
||
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||
dbContext.Database.Migrate();
|
||
}
|
||
|
||
// === 定义 API 端点 (Endpoints) ===
|
||
|
||
|
||
// GET / - 根路径,返回一个简单的消息
|
||
app.MapGet("/", () => "Hello World!");
|
||
app.MapUserEndpoints();
|
||
app.UseAuthentication();
|
||
app.UseAuthorization();
|
||
|
||
//app.MapPatch("/users", async (User user, UserService userService) =>
|
||
//{
|
||
// return await userService.PatchUserAsync(user);
|
||
//});
|
||
//// GET /todos - 获取所有待办事项
|
||
//app.MapGet("/todos", async (AppDbContext dbContext) =>
|
||
//{
|
||
// var todos = await dbContext.Todos.ToListAsync();
|
||
// return TypedResults.Ok(todos);
|
||
//});
|
||
|
||
//// GET /users - 获取所有用户
|
||
//app.MapGet("/users", async (AppDbContext dbContext) =>
|
||
//{
|
||
// var users = await dbContext.Users.ToListAsync();
|
||
// return TypedResults.Ok(users);
|
||
//});
|
||
|
||
//// GET /todos/{id} - 根据 ID 获取单个待办事项
|
||
//app.MapGet("/todos/{id}", async (int id, AppDbContext dbContext) =>
|
||
//{
|
||
// var todo = await dbContext.Todos.FindAsync(id);
|
||
// return todo is not null
|
||
// ? TypedResults.Ok(todo)
|
||
// : Results.NotFound($"待办事项 (ID: {id}) 不存在。");
|
||
//});
|
||
|
||
// GET /users/{id} - 根据 ID 获取单个用户
|
||
//app.MapGet("/users/{id}", async (int id, AppDbContext dbContext) =>
|
||
//{
|
||
// var user = await dbContext.Users.FindAsync(id);
|
||
// return user is not null
|
||
// ? TypedResults.Ok(user)
|
||
// : Results.NotFound($"用户 (ID: {id}) 不存在。");
|
||
//});
|
||
|
||
//// POST /todos - 新增一个待办事项
|
||
//app.MapPost("/todos", async (Todo todo, AppDbContext dbContext) =>
|
||
//{
|
||
// // EF Core 会自动处理 ID 的生成(如果是自增主键)
|
||
// dbContext.Todos.Add(todo);
|
||
// await dbContext.SaveChangesAsync(); // 保存更改到数据库
|
||
|
||
// // 回传 201 Created 并提供新资源的位置
|
||
// return Results.Created($"/todos/{todo.Id}", todo);
|
||
//});
|
||
|
||
|
||
//// POST /users - 新增一个用户
|
||
//app.MapPost("/users", async (User user, AppDbContext dbContext) =>
|
||
//{
|
||
// user.PasswordHash = RandomNumberGenerator.GetString("0123456789",22);
|
||
// Console.WriteLine(user.PasswordHash);
|
||
// user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(user.PasswordHash,10,true); // 哈希密码
|
||
// // EF Core 会自动处理 ID 的生成(如果是自增主键)
|
||
// dbContext.Users.Add(user);
|
||
// await dbContext.SaveChangesAsync(); // 保存更改到数据库
|
||
|
||
// // 回传 201 Created 并提供新资源的位置
|
||
// return Results.Created($"/users/{user.Id}", user);
|
||
//});
|
||
|
||
//// PUT /todos/{id} - 更新指定的待办事项
|
||
//app.MapPut("/todos/{id}", async (int id, Todo inputTodo, AppDbContext dbContext) =>
|
||
//{
|
||
// // 查找现有待办事项
|
||
// var existingTodo = await dbContext.Todos.FindAsync(id);
|
||
|
||
// if (existingTodo is null)
|
||
// {
|
||
// return Results.NotFound($"待办事项 (ID: {id}) 不存在。");
|
||
// }
|
||
|
||
// // 更新属性
|
||
// existingTodo.Title = inputTodo.Title;
|
||
// existingTodo.IsCompleted = inputTodo.IsCompleted;
|
||
|
||
// await dbContext.SaveChangesAsync(); // 保存更改到数据库
|
||
|
||
// return Results.NoContent(); // 回传 204 No Content 表示成功但无内容回传
|
||
//});
|
||
|
||
|
||
//// PUT /users/{id} - 更新指定的用户
|
||
//app.MapPut("/users/{id}", async (int id, User inputUser, AppDbContext dbContext) =>
|
||
//{
|
||
// // 查找现有用户
|
||
// var existingUser = await dbContext.Users.FindAsync(id);
|
||
// if (existingUser is null)
|
||
// {
|
||
// return Results.NotFound($"用户 (ID: {id}) 不存在。");
|
||
// }
|
||
|
||
// // 更新属性
|
||
// existingUser.Username = inputUser.Username;
|
||
// existingUser.Email = inputUser.Email;
|
||
// existingUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(inputUser.PasswordHash,10,true); // 哈希密码
|
||
|
||
// await dbContext.SaveChangesAsync();
|
||
|
||
// return Results.NoContent();
|
||
//});
|
||
|
||
//// DELETE /todos/{id} - 删除指定的待办事项
|
||
//app.MapDelete("/todos/{id}", async (int id, AppDbContext dbContext) =>
|
||
//{
|
||
// var todoToDelete = await dbContext.Todos.FindAsync(id);
|
||
|
||
// if (todoToDelete is null)
|
||
// {
|
||
// return Results.NotFound($"待办事项 (ID: {id}) 不存在。");
|
||
// }
|
||
|
||
// dbContext.Todos.Remove(todoToDelete);
|
||
// await dbContext.SaveChangesAsync(); // 保存更改到数据库
|
||
|
||
// return TypedResults.Ok($"待办事项 (ID: {id}) 已成功删除。");
|
||
//});
|
||
|
||
//// DELETE /users/{id} - 删除指定的用户
|
||
//app.MapDelete("/users/{id}", async (int id, AppDbContext dbContext) =>
|
||
//{
|
||
// var userToDelete = await dbContext.Users.FindAsync(id);
|
||
|
||
// if (userToDelete is null) return Results.NotFound("用户不存在");
|
||
// {
|
||
// dbContext.Users.Remove(userToDelete);
|
||
// await dbContext.SaveChangesAsync(); // 保存更改到数据库
|
||
// return TypedResults.Ok("用户已成功删除");
|
||
// }
|
||
//});
|
||
|
||
// 运行应用程式
|
||
app.Run();
|