From 94642457a8646c791701e97064ca29a33c2d1f79 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 2 Dec 2024 23:41:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BAtoken=E7=9A=84=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E6=80=A7=20=E9=98=B2=E6=AD=A2=E7=9B=97=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controllers/UserController.cs | 12 ++++- src/Middleware/JwtMiddleware.cs | 79 ++++++++++++++++++++----------- src/Models/ClientInfo.cs | 6 +++ src/Program.cs | 2 +- src/SerilogConfig.cs | 2 +- src/Services/AuthService.cs | 18 +++++-- 6 files changed, 84 insertions(+), 35 deletions(-) create mode 100644 src/Models/ClientInfo.cs diff --git a/src/Controllers/UserController.cs b/src/Controllers/UserController.cs index b8c4839..bbbc212 100644 --- a/src/Controllers/UserController.cs +++ b/src/Controllers/UserController.cs @@ -3,6 +3,7 @@ using iFileProxy.Helpers; using iFileProxy.Models; using iFileProxy.Services; using Microsoft.AspNetCore.Mvc; +using System.Linq; namespace iFileProxy.Controllers { @@ -50,7 +51,16 @@ namespace iFileProxy.Controllers try { var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; - var (success, token, message) = await _authService.LoginAsync(request.Username, request.Password, ip); + var userAgent = HttpContext.Request.Headers["User-Agent"].FirstOrDefault() ?? "unknown"; + var fingerprint = HttpContext.Request.Headers["X-Device-Fingerprint"].FirstOrDefault() ?? "unknown"; + + var (success, token, message) = await _authService.LoginAsync( + request.Username, + request.Password, + ip, + userAgent, + fingerprint + ); return Ok(new CommonRsp { diff --git a/src/Middleware/JwtMiddleware.cs b/src/Middleware/JwtMiddleware.cs index 5b33fb4..3aca506 100644 --- a/src/Middleware/JwtMiddleware.cs +++ b/src/Middleware/JwtMiddleware.cs @@ -19,40 +19,65 @@ namespace iFileProxy.Middleware public async Task InvokeAsync(HttpContext context) { var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); + var fingerprint = context.Request.Headers["X-Device-Fingerprint"].FirstOrDefault(); + var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault(); + var ip = context.Connection.RemoteIpAddress?.ToString(); if (token != null) - await AttachUserToContext(context, token); + { + try + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]); + + // 验证token + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = true, + ValidateAudience = true, + ValidIssuer = _configuration["Jwt:Issuer"], + ValidAudience = _configuration["Jwt:Audience"], + ClockSkew = TimeSpan.Zero + }, out SecurityToken validatedToken); + + var jwtToken = (JwtSecurityToken)validatedToken; + + // 验证客户端信息 + var tokenFingerprint = jwtToken.Claims.First(x => x.Type == "fingerprint").Value; + var tokenUserAgent = jwtToken.Claims.First(x => x.Type == "userAgent").Value; + var tokenIp = jwtToken.Claims.First(x => x.Type == "ip").Value; + + // 如果客户端信息不匹配,则拒绝访问 + if (fingerprint != tokenFingerprint || + userAgent != tokenUserAgent || + (ip != tokenIp && !IsLocalNetwork(ip, tokenIp))) // 允许本地网络IP变化 + { + context.Response.StatusCode = 401; + return; + } + + var userId = jwtToken.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; + context.Items["User"] = userId; + } + catch + { + // token验证失败 + } + } await _next(context); } - private async Task AttachUserToContext(HttpContext context, string token) + private bool IsLocalNetwork(string? ip1, string? ip2) { - try - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]); - tokenHandler.ValidateToken(token, new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(key), - ValidateIssuer = true, - ValidateAudience = true, - ValidIssuer = _configuration["Jwt:Issuer"], - ValidAudience = _configuration["Jwt:Audience"], - ClockSkew = TimeSpan.Zero - }, out SecurityToken validatedToken); - - var jwtToken = (JwtSecurityToken)validatedToken; - var userId = jwtToken.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; - - // 将用户信息附加到上下文中 - context.Items["User"] = userId; - } - catch - { - // 令牌验证失败,不附加用户信息 - } + if (string.IsNullOrEmpty(ip1) || string.IsNullOrEmpty(ip2)) return false; + + // 检查是否都是内网IP + return (ip1.StartsWith("192.168.") && ip2.StartsWith("192.168.")) || + (ip1.StartsWith("10.") && ip2.StartsWith("10.")) || + (ip1.StartsWith("172.") && ip2.StartsWith("172.")); } } } \ No newline at end of file diff --git a/src/Models/ClientInfo.cs b/src/Models/ClientInfo.cs new file mode 100644 index 0000000..7b62e9b --- /dev/null +++ b/src/Models/ClientInfo.cs @@ -0,0 +1,6 @@ +public class ClientInfo +{ + public string IP { get; set; } = string.Empty; + public string UserAgent { get; set; } = string.Empty; + public string Fingerprint { get; set; } = string.Empty; // 浏览器指纹 +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 98dd5ef..6af721c 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -27,7 +27,7 @@ namespace iFileProxy builder => { builder - .WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050") + .WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050", "http://localhost:4173") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); diff --git a/src/SerilogConfig.cs b/src/SerilogConfig.cs index 3cbeeb1..0c2db33 100644 --- a/src/SerilogConfig.cs +++ b/src/SerilogConfig.cs @@ -74,7 +74,7 @@ loggerConfiguration.WriteTo.Sink( new DatabaseLogSink( _dbGateService, - LogEventLevel.Error + LogEventLevel.Warning ) ); } diff --git a/src/Services/AuthService.cs b/src/Services/AuthService.cs index b07cf8f..9032874 100644 --- a/src/Services/AuthService.cs +++ b/src/Services/AuthService.cs @@ -67,7 +67,7 @@ namespace iFileProxy.Services } } - public async Task<(bool success, string token, string message)> LoginAsync(string account, string password, string ip) + public async Task<(bool success, string token, string message)> LoginAsync(string account, string password, string ip, string userAgent, string fingerprint) { try { @@ -102,8 +102,13 @@ namespace iFileProxy.Services }; await _dbGateService.CreateUserEventAsync(userEvent); - // 生成JWT令牌 - var token = GenerateJwtToken(user); + // 生成包含客户端信息的token + var token = GenerateJwtToken(user, new ClientInfo + { + IP = ip, + UserAgent = userAgent, + Fingerprint = fingerprint + }); return (true, token, "登录成功"); } @@ -114,13 +119,16 @@ namespace iFileProxy.Services } } - private string GenerateJwtToken(User user) + private string GenerateJwtToken(User user, ClientInfo clientInfo) { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.UserId), new Claim(ClaimTypes.Name, user.Username), - new Claim(ClaimTypes.Role, user.Mask.ToString()) + new Claim(ClaimTypes.Role, user.Mask.ToString()), + new Claim("ip", clientInfo.IP), + new Claim("userAgent", clientInfo.UserAgent), + new Claim("fingerprint", clientInfo.Fingerprint) }; // 确保密钥至少32字节长