From fc5fb61ae802fa6baa18f00049021a48722d02ac Mon Sep 17 00:00:00 2001 From: milimoe Date: Wed, 12 Mar 2025 21:54:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20JWT=20Token=20=E5=90=8A?= =?UTF-8?q?=E9=94=80=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DataRequestController.cs | 2 + .../JwtAuthenticationMiddleware.cs | 26 ++++++++++ .../Controllers/AdapterController.cs | 5 ++ .../Controllers/DataRequestController.cs | 14 ++++++ .../Controllers/GamingRequestController.cs | 5 ++ FunGame.WebAPI/Controllers/UserController.cs | 48 ++++++++++++++----- FunGame.WebAPI/Program.cs | 10 ++-- FunGame.WebAPI/Services/JWTService.cs | 29 ++++++++++- 8 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 FunGame.WebAPI/Architecture/JwtAuthenticationMiddleware.cs diff --git a/FunGame.Server/Controllers/DataRequestController.cs b/FunGame.Server/Controllers/DataRequestController.cs index b060869..523363b 100644 --- a/FunGame.Server/Controllers/DataRequestController.cs +++ b/FunGame.Server/Controllers/DataRequestController.cs @@ -569,6 +569,7 @@ namespace Milimoe.FunGame.Server.Controller // 验证登录 if (username != null && password != null) { + password = password.Encrypt(username); ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] Username: " + username); if (SQLHelper != null) { @@ -769,6 +770,7 @@ namespace Milimoe.FunGame.Server.Controller string password = DataRequest.GetDictionaryJsonObject(requestData, UserQuery.Column_Password) ?? ""; if (username.Trim() != "" && password.Trim() != "") { + password = password.Encrypt(username); SQLHelper?.Execute(UserQuery.Update_Password(SQLHelper, username, password)); if (SQLHelper?.Success ?? false) { diff --git a/FunGame.WebAPI/Architecture/JwtAuthenticationMiddleware.cs b/FunGame.WebAPI/Architecture/JwtAuthenticationMiddleware.cs new file mode 100644 index 0000000..2036921 --- /dev/null +++ b/FunGame.WebAPI/Architecture/JwtAuthenticationMiddleware.cs @@ -0,0 +1,26 @@ +using Milimoe.FunGame.WebAPI.Services; + +namespace Milimoe.FunGame.WebAPI.Architecture +{ + public class JwtAuthenticationMiddleware(RequestDelegate next) + { + public async Task InvokeAsync(HttpContext context) + { + JWTService jwtService = context.RequestServices.GetRequiredService(); + + // 获取 JWT Token + string token = context.Request.Headers.Authorization.ToString().Replace("Bearer ", ""); + + // 检查 JWT 是否被吊销 + if (jwtService.IsTokenRevoked(token)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync("{\"message\":\"此 Token 已吊销,请重新登录以获取 Token。\"}"); + return; + } + + await next(context); + } + } +} diff --git a/FunGame.WebAPI/Controllers/AdapterController.cs b/FunGame.WebAPI/Controllers/AdapterController.cs index 32100c4..0e1d0d7 100644 --- a/FunGame.WebAPI/Controllers/AdapterController.cs +++ b/FunGame.WebAPI/Controllers/AdapterController.cs @@ -54,6 +54,11 @@ namespace Milimoe.FunGame.WebAPI.Controllers } return BadRequest(new SocketObject(SocketMessageType.System, model.Token, "δִϣȴ")); } + catch (TimeoutException) + { + _logger.LogWarning("ʱ"); + return StatusCode(408); + } catch (Exception e) { _logger.LogError("Error: {e}", e); diff --git a/FunGame.WebAPI/Controllers/DataRequestController.cs b/FunGame.WebAPI/Controllers/DataRequestController.cs index 0869b3f..4da774d 100644 --- a/FunGame.WebAPI/Controllers/DataRequestController.cs +++ b/FunGame.WebAPI/Controllers/DataRequestController.cs @@ -21,6 +21,15 @@ namespace Milimoe.FunGame.WebAPI.Controllers { RequestType = payload.RequestType }; + + if (payload.RequestType == DataRequestType.RunTime_Logout || payload.RequestType == DataRequestType.Reg_Reg || + payload.RequestType == DataRequestType.Login_Login || payload.RequestType == DataRequestType.Login_GetFindPasswordVerifyCode) + { + response.StatusCode = 400; + response.Message = $" {DataRequestSet.GetTypeString(payload.RequestType)} ͨ˽ӿڴ"; + return StatusCode(400, response); + } + if (model.RequestID == Guid.Empty) { Guid uid = Guid.NewGuid(); @@ -58,6 +67,11 @@ namespace Milimoe.FunGame.WebAPI.Controllers return BadRequest(response); } } + catch (TimeoutException) + { + _logger.LogWarning("ʱ"); + return StatusCode(408); + } catch (Exception e) { _logger.LogError("Error: {e}", e); diff --git a/FunGame.WebAPI/Controllers/GamingRequestController.cs b/FunGame.WebAPI/Controllers/GamingRequestController.cs index 8cdbe48..28e6247 100644 --- a/FunGame.WebAPI/Controllers/GamingRequestController.cs +++ b/FunGame.WebAPI/Controllers/GamingRequestController.cs @@ -67,6 +67,11 @@ namespace Milimoe.FunGame.WebAPI.Controllers return BadRequest(response); } } + catch (TimeoutException) + { + _logger.LogWarning("ʱ"); + return StatusCode(408); + } catch (Exception e) { _logger.LogError("Error: {e}", e); diff --git a/FunGame.WebAPI/Controllers/UserController.cs b/FunGame.WebAPI/Controllers/UserController.cs index 086ef72..b4406c8 100644 --- a/FunGame.WebAPI/Controllers/UserController.cs +++ b/FunGame.WebAPI/Controllers/UserController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Library.Constant; @@ -13,10 +14,8 @@ namespace Milimoe.FunGame.WebAPI.Controllers { [ApiController] [Route("[controller]")] - public class UserController(JWTService jwtTokenService, ILogger logger) : ControllerBase + public class UserController(RESTfulAPIListener apiListener, JWTService jwtTokenService, ILogger logger) : ControllerBase { - private readonly ILogger _logger = logger; - [HttpPost("reg")] public IActionResult Reg([FromBody] RegDTO dto) { @@ -46,7 +45,7 @@ namespace Milimoe.FunGame.WebAPI.Controllers } catch (Exception e) { - _logger.LogError("Error: {e}", e); + logger.LogError("Error: {e}", e); } return BadRequest("服务器暂时无法处理注册请求。"); } @@ -56,12 +55,17 @@ namespace Milimoe.FunGame.WebAPI.Controllers { try { + PayloadModel response = new() + { + RequestType = DataRequestType.Login_Login + }; string msg = "用户名或密码不正确。"; + string clientIP = HttpContext.Connection.RemoteIpAddress?.ToString() + ":" + HttpContext.Connection.RemotePort; ServerHelper.WriteLine(ServerHelper.MakeClientName(clientIP) + " 通过 RESTful API 连接至服务器,正在登录 . . .", InvokeMessageType.Core); string username = dto.Username; string password = dto.Password; - RESTfulAPIListener? apiListener = RESTfulAPIListener.Instance; + if (apiListener != null) { // 移除旧模型 @@ -90,7 +94,14 @@ namespace Milimoe.FunGame.WebAPI.Controllers model.GetUsersCount(); string token = jwtTokenService.GenerateToken(username); Config.ConnectingPlayerCount--; - return Ok(new { BearerToken = token, OpenToken = model.Token }); + response.StatusCode = 200; + response.Message = "登录成功!"; + response.Data = new() + { + { "bearerToken", token }, + { "openToken", model.Token } + }; + return Ok(response); } } else msg = "服务器暂时无法处理登录请求。"; @@ -100,26 +111,37 @@ namespace Milimoe.FunGame.WebAPI.Controllers Config.ConnectingPlayerCount--; ServerHelper.WriteLine(msg, InvokeMessageType.Core); - return Unauthorized(msg); + response.Message = msg; + response.StatusCode = 401; + return Unauthorized(response); } catch (Exception e) { - _logger.LogError("Error: {e}", e); + logger.LogError("Error: {e}", e); } - return BadRequest("服务器暂时无法处理登录请求。"); + return BadRequest(); } [HttpPost("refresh")] [Authorize] - public IActionResult Refresh([FromBody] LoginDTO dto) + public IActionResult Refresh() { try { - return Ok(jwtTokenService.GenerateToken(dto.Username)); + string oldToken = HttpContext.Request.Headers.Authorization.ToString().Replace("Bearer ", ""); + + // 吊销 + jwtTokenService.RevokeToken(oldToken); + + // 生成 + string username = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? ""; + string newToken = jwtTokenService.GenerateToken(username); + + return Ok(newToken); } catch (Exception e) { - _logger.LogError("Error: {e}", e); + logger.LogError("Error: {e}", e); } return BadRequest(); } diff --git a/FunGame.WebAPI/Program.cs b/FunGame.WebAPI/Program.cs index 23fc23d..5f4d1dd 100644 --- a/FunGame.WebAPI/Program.cs +++ b/FunGame.WebAPI/Program.cs @@ -1,5 +1,6 @@ using System.Net.WebSockets; using System.Reflection; +using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Json.Serialization; @@ -146,7 +147,8 @@ try ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] ?? "undefined")) + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] ?? "undefined")), + NameClaimType = ClaimTypes.NameIdentifier }; }).AddScheme("CustomBearer", options => { }); builder.Logging.AddConsole(options => @@ -155,6 +157,8 @@ try }); builder.Services.AddSingleton(); // 其他依赖注入 + builder.Services.AddHttpClient(); + builder.Services.AddMemoryCache(); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddSingleton(listener); @@ -174,8 +178,6 @@ try throw new NoUserLogonException(); }); - builder.Services.AddHttpClient(); - WebApplication app = builder.Build(); // 启用 CORS @@ -194,6 +196,8 @@ try app.UseHttpsRedirection(); + app.UseMiddleware(); + app.UseAuthorization(); app.MapControllers(); diff --git a/FunGame.WebAPI/Services/JWTService.cs b/FunGame.WebAPI/Services/JWTService.cs index 0068985..f5c72b3 100644 --- a/FunGame.WebAPI/Services/JWTService.cs +++ b/FunGame.WebAPI/Services/JWTService.cs @@ -1,11 +1,12 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using Microsoft.Extensions.Caching.Memory; using Microsoft.IdentityModel.Tokens; using Milimoe.FunGame.Core.Library.Constant; namespace Milimoe.FunGame.WebAPI.Services { - public class JWTService(IConfiguration configuration) + public class JWTService(IConfiguration configuration, IMemoryCache memoryCache) { public string GenerateToken(string username) { @@ -29,5 +30,31 @@ namespace Milimoe.FunGame.WebAPI.Services return new JwtSecurityTokenHandler().WriteToken(token); } + + public void RevokeToken(string token) + { + // 从 Token 中提取过期时间 + JwtSecurityToken jwtSecurityToken = new JwtSecurityTokenHandler().ReadJwtToken(token); + string? expiryClaim = jwtSecurityToken.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value; + + if (expiryClaim != null && long.TryParse(expiryClaim, out long expiryUnixTimestamp)) + { + DateTime expiryDateTime = DateTimeOffset.FromUnixTimeSeconds(expiryUnixTimestamp).LocalDateTime; + TimeSpan remainingTime = expiryDateTime - DateTime.Now; + + // 将 Token 存储到 MemoryCache 中,过期时间为 Token 的剩余有效期 + memoryCache.Set(token, true, remainingTime); + } + else + { + memoryCache.Set(token, true, TimeSpan.FromMinutes(30)); + } + } + + public bool IsTokenRevoked(string token) + { + // 检查 Token 是否被吊销 + return memoryCache.TryGetValue(token, out _); + } } }