增强token的安全性 防止盗用
This commit is contained in:
parent
d314597e8d
commit
94642457a8
6 changed files with 84 additions and 35 deletions
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
6
src/Models/ClientInfo.cs
Normal 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; // 浏览器指纹
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
loggerConfiguration.WriteTo.Sink(
|
||||
new DatabaseLogSink(
|
||||
_dbGateService,
|
||||
LogEventLevel.Error
|
||||
LogEventLevel.Warning
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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字节长
|
||||
|
|
Loading…
Reference in a new issue