Files
2026-03-25 14:55:34 +08:00

175 lines
5.7 KiB
C#

using System.Security.Claims;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using StopShopping.EF;
using StopShopping.EF.Models;
using StopShopping.Services.Models;
using StopShopping.Services.Models.Resp;
namespace StopShopping.Services.Implementions;
public class AccessTokenService : IAccessTokenService
{
public AccessTokenService(IOptions<JwtOptions> jwtOptions,
IDistributedCache cache,
StopShoppingContext dbContext,
ILogger<AccessTokenService> logger,
IClaimsService claimsService)
{
_jwtOptions = jwtOptions.Value;
_cache = cache;
_dbContext = dbContext;
_logger = logger;
_claimsService = claimsService;
}
private readonly JwtOptions _jwtOptions;
private readonly IDistributedCache _cache;
private readonly StopShoppingContext _dbContext;
private readonly ILogger<AccessTokenService> _logger;
private readonly IClaimsService _claimsService;
public AccessToken GenerateAccessToken(ClaimsIdentity claims)
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = _jwtOptions.ValidAudience,
Issuer = _jwtOptions.ValidIssuer,
Subject = claims,
IssuedAt = DateTime.UtcNow,
NotBefore = DateTime.UtcNow,
Expires = DateTime.UtcNow.AddSeconds(_jwtOptions.AccessTokenExpiresIn),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SigningKey!)),
SecurityAlgorithms.HmacSha256
)
};
var token = new JsonWebTokenHandler()
.CreateToken(tokenDescriptor);
return new AccessToken
{
Token = token,
ExpiresIn = _jwtOptions.AccessTokenExpiresIn
};
}
public async Task<bool> AddAccessTokenBlacklistAsync(string accessToken)
{
JsonWebTokenHandler jwtHandler = new JsonWebTokenHandler();
var jwtToken = jwtHandler.ReadJsonWebToken(accessToken);
var expClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Exp);
if (null == expClaim)
{
_logger.LogError("access_token:{Token}中无法找到exp", accessToken);
return false;
}
var expTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expClaim.Value)).UtcDateTime;
if (DateTime.UtcNow > expTime)
{
_logger.LogError("access_token:{Token}已过期", accessToken);
return false;
}
await _cache.SetStringAsync(Consts.CacheKeys.AccessTokenBlacklist(accessToken),
"quited user",
new DistributedCacheEntryOptions
{
AbsoluteExpiration = expTime
});
return true;
}
public async Task<bool> IsAccessTokenBlacklistAsync(string accessToken)
{
var blacklist = await _cache.GetStringAsync(Consts.CacheKeys.AccessTokenBlacklist(accessToken));
return !string.IsNullOrWhiteSpace(blacklist);
}
public async Task<AccessToken> SetRefreshTokenAsync(int userId, SystemRoles systemRole)
{
var refreshToken = Guid.NewGuid().ToString("N");
var now = DateTime.Now;
char role = (char)systemRole;
// var qry = await _dbContext.RefreshTokens
// .Where(r => r.UserId == userId && r.SystemRole == role)
// .ExecuteDeleteAsync();
var model = new RefreshToken
{
CreateTime = now,
ExpiresAt = now.AddSeconds(_jwtOptions.RefreshTokenExpiresIn),
SystemRole = role,
Token = refreshToken,
UserId = userId
};
await _dbContext.RefreshTokens.AddAsync(model);
await _dbContext.SaveChangesAsync();
return new AccessToken
{
Token = refreshToken,
ExpiresIn = _jwtOptions.RefreshTokenExpiresIn
};
}
public async Task RevokeRefreshTokenAsync(string refreshToken)
{
await _dbContext.RefreshTokens
.Where(r => r.Token == refreshToken)
.ExecuteDeleteAsync();
}
public async Task<AccessToken?> GenerateAccessTokenAsync(string refreshToken)
{
var rt = await _dbContext.RefreshTokens
.AsNoTracking()
.Where(r => r.Token == refreshToken && r.ExpiresAt > DateTime.Now)
.FirstOrDefaultAsync();
if (null == rt)
return null;
ClaimsIdentity? claimsIdentity = null;
switch (rt.SystemRole)
{
case (char)SystemRoles.Admin:
{
var admin = await _dbContext.Administrators
.AsNoTracking()
.Where(a => a.Id == rt.UserId)
.FirstOrDefaultAsync();
if (null != admin)
claimsIdentity = _claimsService.BuildAdminIdentity(admin);
}
break;
case (char)SystemRoles.User:
{
var user = await _dbContext.Users
.AsNoTracking()
.Where(u => u.Id == rt.UserId)
.FirstOrDefaultAsync();
if (null != user)
claimsIdentity = _claimsService.BuildIdentity(user);
}
break;
default: break;
}
if (null == claimsIdentity)
return null;
return GenerateAccessToken(claimsIdentity);
}
}