登录功能
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 30s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s

This commit is contained in:
孙诚
2025-12-25 13:27:23 +08:00
parent ebb49577dd
commit 728c39f43d
16 changed files with 395 additions and 23 deletions

View File

@@ -0,0 +1,86 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Service.AppSettingModel;
namespace WebApi.Controllers;
[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
private readonly AuthSettings _authSettings;
private readonly JwtSettings _jwtSettings;
private readonly ILogger<AuthController> _logger;
public AuthController(
IOptions<AuthSettings> authSettings,
IOptions<JwtSettings> jwtSettings,
ILogger<AuthController> logger)
{
_authSettings = authSettings.Value;
_jwtSettings = jwtSettings.Value;
_logger = logger;
}
/// <summary>
/// 用户登录
/// </summary>
[AllowAnonymous]
[HttpPost]
public BaseResponse<LoginResponse> Login([FromBody] LoginRequest request)
{
// 验证密码
if (string.IsNullOrEmpty(request.Password) || request.Password != _authSettings.Password)
{
_logger.LogWarning("登录失败: 密码错误");
return new BaseResponse<LoginResponse>
{
Success = false,
Message = "密码错误"
};
}
// 生成JWT Token
var token = GenerateJwtToken();
var expiresAt = DateTime.UtcNow.AddHours(_jwtSettings.ExpirationHours);
_logger.LogInformation("用户登录成功");
return new BaseResponse<LoginResponse>
{
Success = true,
Data = new LoginResponse
{
Token = token,
ExpiresAt = expiresAt
}
};
}
private string GenerateJwtToken()
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()),
new Claim("auth", "password-auth")
};
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddHours(_jwtSettings.ExpirationHours),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}

View File

@@ -0,0 +1,6 @@
namespace WebApi.Controllers.Dto;
public class LoginRequest
{
public string Password { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,7 @@
namespace WebApi.Controllers.Dto;
public class LoginResponse
{
public string Token { get; set; } = string.Empty;
public DateTime ExpiresAt { get; set; }
}

View File

@@ -1,4 +1,7 @@
using System.Text;
using FreeSql;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Scalar.AspNetCore;
using Serilog;
using Service.AppSettingModel;
@@ -35,6 +38,35 @@ builder.Services.AddCors(options =>
// 绑定配置
builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("EmailSettings"));
builder.Services.Configure<AISettings>(builder.Configuration.GetSection("OpenAI"));
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection("AuthSettings"));
// 配置JWT认证
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = jwtSettings["SecretKey"]!;
var key = Encoding.UTF8.GetBytes(secretKey);
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 = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key),
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
// 配置 FreeSql + SQLite
var dbPath = Path.Combine(AppContext.BaseDirectory, "database");
@@ -81,6 +113,10 @@ app.UseStaticFiles();
// 启用 CORS
app.UseCors();
// 启用认证和授权
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// 添加 SPA 回退路由(用于前端路由)

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="FreeSql.Provider.Sqlite" />

View File

@@ -48,5 +48,14 @@
"95555@message.cmbchina.com",
"ccsvc@message.cmbchina.com"
]
},
"JwtSettings": {
"SecretKey": "6CA57F7D-B73F-AABC-007C-D2DF98E319DF-07802A80-1982-64CD-1CFE-466728053850",
"Issuer": "EmailBillApi",
"Audience": "EmailBillWeb",
"ExpirationHours": 7200
},
"AuthSettings": {
"Password": "SCsunch940622"
}
}