增强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.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
{

View file

@ -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."));
}
}
}

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
.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();

View file

@ -74,7 +74,7 @@
loggerConfiguration.WriteTo.Sink(
new DatabaseLogSink(
_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
{
@ -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字节长