添加项目文件。

This commit is contained in:
2026-05-09 17:06:30 +08:00
parent c60f5fe117
commit 720ef957d4
378 changed files with 14843 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.25" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
</ItemGroup>
</Project>
+18
View File
@@ -0,0 +1,18 @@
using System.Security.Claims;
namespace IM.Jwt
{
public interface ITokenService
{
/// <summary>
/// 获取令牌
/// </summary>
/// <param name="claims">令牌负载</param>
/// <param name="options"></param>
/// <returns></returns>
string GetToken(IEnumerable<Claim> claims, JwtOptions options);
Task<string> CreateRefreshTokenAsync(Guid userId, CancellationToken cancellationToken = default);
Task RevokeRefreshTokenAsync(string refreshToken);
Task<(bool ok, Guid userId)> ValidateRefreshTokenAsync(string token, CancellationToken cancellation = default);
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace IM.Jwt
{
public class JwtOptions
{
public string Key { get; init; }
public string Issuer { get; init; }
public string Audience { get; init; }
public int AccessTokenMinutes { get; init; }
public int RefreshTokenDays { get; init; }
}
}
+13
View File
@@ -0,0 +1,13 @@
using IM.Commons;
using Microsoft.Extensions.DependencyInjection;
namespace IM.Jwt
{
public class ModuleInit : IModuleInitializer
{
public void Initialize(IServiceCollection services)
{
services.AddScoped<ITokenService, TokenService>();
}
}
}
+70
View File
@@ -0,0 +1,70 @@
using IM.Commons;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using StackExchange.Redis;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace IM.Jwt
{
public class TokenService : ITokenService
{
private readonly IDatabase _redis;
private readonly IOptions<JwtOptions> _options;
public TokenService(IConnectionMultiplexer multiplexer, IOptions<JwtOptions> options)
{
_redis = multiplexer.GetDatabase();
_options = options;
}
private static string GenerateTokenStr()
{
var bytes = RandomNumberGenerator.GetBytes(32);
return Convert.ToBase64String(bytes);
}
public async Task<string> CreateRefreshTokenAsync(Guid userId, CancellationToken cancellationToken = default)
{
string token = GenerateTokenStr();
var payload = new { UserId = userId, CreateAt = DateTime.Now };
string json = JsonConvert.SerializeObject(payload);
//token写入redis
await _redis.StringSetAsync(RedisHelper.GetRefreshTokenKey(token), json, TimeSpan.FromDays(_options.Value.RefreshTokenDays));
return token;
}
public string GetToken(IEnumerable<Claim> claims, JwtOptions options)
{
TimeSpan ExpiryDuration = TimeSpan.FromMinutes(options.AccessTokenMinutes);
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(options.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new JwtSecurityToken(options.Issuer, options.Audience, claims,
expires: DateTime.Now.Add(ExpiryDuration), signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
}
public async Task RevokeRefreshTokenAsync(string refreshToken)
{
await _redis.KeyDeleteAsync(RedisHelper.GetRefreshTokenKey(refreshToken));
}
public async Task<(bool ok, Guid userId)> ValidateRefreshTokenAsync(string token, CancellationToken cancellation = default)
{
var json = await _redis.StringGetAsync(RedisHelper.GetRefreshTokenKey(token));
if (json.IsNullOrEmpty) return (false, Guid.Empty);
try
{
using var doc = JsonDocument.Parse(json.ToString());
var userId = doc.RootElement.GetProperty("UserId").GetGuid();
return (true, userId);
}
catch
{
return (false, Guid.Empty);
}
}
}
}
+82
View File
@@ -0,0 +1,82 @@
using IM.Commons;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace IM.Jwt
{
public static class WebApplicationJwtExtension
{
public static IServiceCollection AddJwt(this IServiceCollection services, JwtOptions jwtOptions)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtOptions.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
// 验证签名秘钥(防止 Token 被篡改)
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Key)),
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hub")) // 假设你的 SignalR 路径是 /hub
{
context.Token = accessToken;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
// 在这里打断点,查看 context.Exception
// 常见的有:SecurityTokenExpiredException (过期)
// 或 SecurityTokenInvalidSignatureException (密钥不对)
Console.WriteLine("验证失败原因: " + context.Exception.Message);
return Task.CompletedTask;
},
OnChallenge = async context =>
{
context.HandleResponse();
context.Response.ContentType = "application/json";
context.Response.StatusCode = StatusCodes.Status200OK;
var result = Result<object>.Fail(ResultCode.AUTH_FAILED);
await context.Response.WriteAsJsonAsync(result);
},
OnForbidden = async context =>
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = StatusCodes.Status200OK;
var result = Result<object>.Fail(ResultCode.PERMISSION_DENIED);
await context.Response.WriteAsJsonAsync(result);
}
};
});
return services;
}
}
}