增强token的安全性 防止盗用

This commit is contained in:
root 2024-12-02 23:41:24 +08:00
parent d314597e8d
commit 94642457a8
6 changed files with 84 additions and 35 deletions

View file

@ -3,6 +3,7 @@ using iFileProxy.Helpers;
using iFileProxy.Models; using iFileProxy.Models;
using iFileProxy.Services; using iFileProxy.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace iFileProxy.Controllers namespace iFileProxy.Controllers
{ {
@ -50,7 +51,16 @@ namespace iFileProxy.Controllers
try try
{ {
var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; 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 return Ok(new CommonRsp
{ {

View file

@ -19,40 +19,65 @@ namespace iFileProxy.Middleware
public async Task InvokeAsync(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); 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) 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); await _next(context);
} }
private async Task AttachUserToContext(HttpContext context, string token) private bool IsLocalNetwork(string? ip1, string? ip2)
{ {
try if (string.IsNullOrEmpty(ip1) || string.IsNullOrEmpty(ip2)) return false;
{
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; // 检查是否都是内网IP
var userId = jwtToken.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; return (ip1.StartsWith("192.168.") && ip2.StartsWith("192.168.")) ||
(ip1.StartsWith("10.") && ip2.StartsWith("10.")) ||
// 将用户信息附加到上下文中 (ip1.StartsWith("172.") && ip2.StartsWith("172."));
context.Items["User"] = userId;
}
catch
{
// 令牌验证失败,不附加用户信息
}
} }
} }
} }

6
src/Models/ClientInfo.cs Normal file
View file

@ -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; // 浏览器指纹
}

View file

@ -27,7 +27,7 @@ namespace iFileProxy
builder => builder =>
{ {
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() .AllowAnyMethod()
.AllowAnyHeader() .AllowAnyHeader()
.AllowCredentials(); .AllowCredentials();

View file

@ -74,7 +74,7 @@
loggerConfiguration.WriteTo.Sink( loggerConfiguration.WriteTo.Sink(
new DatabaseLogSink( new DatabaseLogSink(
_dbGateService, _dbGateService,
LogEventLevel.Error LogEventLevel.Warning
) )
); );
} }

View file

@ -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 try
{ {
@ -102,8 +102,13 @@ namespace iFileProxy.Services
}; };
await _dbGateService.CreateUserEventAsync(userEvent); await _dbGateService.CreateUserEventAsync(userEvent);
// 生成JWT令牌 // 生成包含客户端信息的token
var token = GenerateJwtToken(user); var token = GenerateJwtToken(user, new ClientInfo
{
IP = ip,
UserAgent = userAgent,
Fingerprint = fingerprint
});
return (true, token, "登录成功"); 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[] var claims = new[]
{ {
new Claim(ClaimTypes.NameIdentifier, user.UserId), new Claim(ClaimTypes.NameIdentifier, user.UserId),
new Claim(ClaimTypes.Name, user.Username), 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字节长 // 确保密钥至少32字节长