mirror of
https://github.com/project-redbud/FunGame-Server.git
synced 2025-04-21 19:49:36 +08:00
添加 JWT Token 吊销机制
This commit is contained in:
parent
b7b0ac1f37
commit
fc5fb61ae8
@ -569,6 +569,7 @@ namespace Milimoe.FunGame.Server.Controller
|
|||||||
// 验证登录
|
// 验证登录
|
||||||
if (username != null && password != null)
|
if (username != null && password != null)
|
||||||
{
|
{
|
||||||
|
password = password.Encrypt(username);
|
||||||
ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] Username: " + username);
|
ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] Username: " + username);
|
||||||
if (SQLHelper != null)
|
if (SQLHelper != null)
|
||||||
{
|
{
|
||||||
@ -769,6 +770,7 @@ namespace Milimoe.FunGame.Server.Controller
|
|||||||
string password = DataRequest.GetDictionaryJsonObject<string>(requestData, UserQuery.Column_Password) ?? "";
|
string password = DataRequest.GetDictionaryJsonObject<string>(requestData, UserQuery.Column_Password) ?? "";
|
||||||
if (username.Trim() != "" && password.Trim() != "")
|
if (username.Trim() != "" && password.Trim() != "")
|
||||||
{
|
{
|
||||||
|
password = password.Encrypt(username);
|
||||||
SQLHelper?.Execute(UserQuery.Update_Password(SQLHelper, username, password));
|
SQLHelper?.Execute(UserQuery.Update_Password(SQLHelper, username, password));
|
||||||
if (SQLHelper?.Success ?? false)
|
if (SQLHelper?.Success ?? false)
|
||||||
{
|
{
|
||||||
|
26
FunGame.WebAPI/Architecture/JwtAuthenticationMiddleware.cs
Normal file
26
FunGame.WebAPI/Architecture/JwtAuthenticationMiddleware.cs
Normal file
@ -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<JWTService>();
|
||||||
|
|
||||||
|
// 获取 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,11 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
}
|
}
|
||||||
return BadRequest(new SocketObject(SocketMessageType.System, model.Token, "请求未执行完毕,请等待!"));
|
return BadRequest(new SocketObject(SocketMessageType.System, model.Token, "请求未执行完毕,请等待!"));
|
||||||
}
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("请求超时。");
|
||||||
|
return StatusCode(408);
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error: {e}", e);
|
_logger.LogError("Error: {e}", e);
|
||||||
|
@ -21,6 +21,15 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
{
|
{
|
||||||
RequestType = payload.RequestType
|
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)
|
if (model.RequestID == Guid.Empty)
|
||||||
{
|
{
|
||||||
Guid uid = Guid.NewGuid();
|
Guid uid = Guid.NewGuid();
|
||||||
@ -58,6 +67,11 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
return BadRequest(response);
|
return BadRequest(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("请求超时。");
|
||||||
|
return StatusCode(408);
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error: {e}", e);
|
_logger.LogError("Error: {e}", e);
|
||||||
|
@ -67,6 +67,11 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
return BadRequest(response);
|
return BadRequest(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("请求超时。");
|
||||||
|
return StatusCode(408);
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error: {e}", e);
|
_logger.LogError("Error: {e}", e);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Milimoe.FunGame.Core.Api.Utility;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
@ -13,10 +14,8 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
public class UserController(JWTService jwtTokenService, ILogger<AdapterController> logger) : ControllerBase
|
public class UserController(RESTfulAPIListener apiListener, JWTService jwtTokenService, ILogger<AdapterController> logger) : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<AdapterController> _logger = logger;
|
|
||||||
|
|
||||||
[HttpPost("reg")]
|
[HttpPost("reg")]
|
||||||
public IActionResult Reg([FromBody] RegDTO dto)
|
public IActionResult Reg([FromBody] RegDTO dto)
|
||||||
{
|
{
|
||||||
@ -46,7 +45,7 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error: {e}", e);
|
logger.LogError("Error: {e}", e);
|
||||||
}
|
}
|
||||||
return BadRequest("服务器暂时无法处理注册请求。");
|
return BadRequest("服务器暂时无法处理注册请求。");
|
||||||
}
|
}
|
||||||
@ -56,12 +55,17 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
PayloadModel<DataRequestType> response = new()
|
||||||
|
{
|
||||||
|
RequestType = DataRequestType.Login_Login
|
||||||
|
};
|
||||||
string msg = "用户名或密码不正确。";
|
string msg = "用户名或密码不正确。";
|
||||||
|
|
||||||
string clientIP = HttpContext.Connection.RemoteIpAddress?.ToString() + ":" + HttpContext.Connection.RemotePort;
|
string clientIP = HttpContext.Connection.RemoteIpAddress?.ToString() + ":" + HttpContext.Connection.RemotePort;
|
||||||
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientIP) + " 通过 RESTful API 连接至服务器,正在登录 . . .", InvokeMessageType.Core);
|
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientIP) + " 通过 RESTful API 连接至服务器,正在登录 . . .", InvokeMessageType.Core);
|
||||||
string username = dto.Username;
|
string username = dto.Username;
|
||||||
string password = dto.Password;
|
string password = dto.Password;
|
||||||
RESTfulAPIListener? apiListener = RESTfulAPIListener.Instance;
|
|
||||||
if (apiListener != null)
|
if (apiListener != null)
|
||||||
{
|
{
|
||||||
// 移除旧模型
|
// 移除旧模型
|
||||||
@ -90,7 +94,14 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
model.GetUsersCount();
|
model.GetUsersCount();
|
||||||
string token = jwtTokenService.GenerateToken(username);
|
string token = jwtTokenService.GenerateToken(username);
|
||||||
Config.ConnectingPlayerCount--;
|
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 = "服务器暂时无法处理登录请求。";
|
else msg = "服务器暂时无法处理登录请求。";
|
||||||
@ -100,26 +111,37 @@ namespace Milimoe.FunGame.WebAPI.Controllers
|
|||||||
|
|
||||||
Config.ConnectingPlayerCount--;
|
Config.ConnectingPlayerCount--;
|
||||||
ServerHelper.WriteLine(msg, InvokeMessageType.Core);
|
ServerHelper.WriteLine(msg, InvokeMessageType.Core);
|
||||||
return Unauthorized(msg);
|
response.Message = msg;
|
||||||
|
response.StatusCode = 401;
|
||||||
|
return Unauthorized(response);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error: {e}", e);
|
logger.LogError("Error: {e}", e);
|
||||||
}
|
}
|
||||||
return BadRequest("服务器暂时无法处理登录请求。");
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("refresh")]
|
[HttpPost("refresh")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public IActionResult Refresh([FromBody] LoginDTO dto)
|
public IActionResult Refresh()
|
||||||
{
|
{
|
||||||
try
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error: {e}", e);
|
logger.LogError("Error: {e}", e);
|
||||||
}
|
}
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@ -146,7 +147,8 @@ try
|
|||||||
ValidateIssuerSigningKey = true,
|
ValidateIssuerSigningKey = true,
|
||||||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
||||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
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<AuthenticationSchemeOptions, CustomBearerAuthenticationHandler>("CustomBearer", options => { });
|
}).AddScheme<AuthenticationSchemeOptions, CustomBearerAuthenticationHandler>("CustomBearer", options => { });
|
||||||
builder.Logging.AddConsole(options =>
|
builder.Logging.AddConsole(options =>
|
||||||
@ -155,6 +157,8 @@ try
|
|||||||
});
|
});
|
||||||
builder.Services.AddSingleton<ConsoleFormatter, CustomConsoleFormatter>();
|
builder.Services.AddSingleton<ConsoleFormatter, CustomConsoleFormatter>();
|
||||||
// 其他依赖注入
|
// 其他依赖注入
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddScoped<IUserContext, HttpUserContext>();
|
builder.Services.AddScoped<IUserContext, HttpUserContext>();
|
||||||
builder.Services.AddSingleton(listener);
|
builder.Services.AddSingleton(listener);
|
||||||
@ -174,8 +178,6 @@ try
|
|||||||
throw new NoUserLogonException();
|
throw new NoUserLogonException();
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddHttpClient();
|
|
||||||
|
|
||||||
WebApplication app = builder.Build();
|
WebApplication app = builder.Build();
|
||||||
|
|
||||||
// 启用 CORS
|
// 启用 CORS
|
||||||
@ -194,6 +196,8 @@ try
|
|||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseMiddleware<JwtAuthenticationMiddleware>();
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.WebAPI.Services
|
namespace Milimoe.FunGame.WebAPI.Services
|
||||||
{
|
{
|
||||||
public class JWTService(IConfiguration configuration)
|
public class JWTService(IConfiguration configuration, IMemoryCache memoryCache)
|
||||||
{
|
{
|
||||||
public string GenerateToken(string username)
|
public string GenerateToken(string username)
|
||||||
{
|
{
|
||||||
@ -29,5 +30,31 @@ namespace Milimoe.FunGame.WebAPI.Services
|
|||||||
|
|
||||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
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 _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user