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, IDistributedCache cache, StopShoppingContext dbContext, ILogger 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 _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 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 IsAccessTokenBlacklistAsync(string accessToken) { var blacklist = await _cache.GetStringAsync(Consts.CacheKeys.AccessTokenBlacklist(accessToken)); return !string.IsNullOrWhiteSpace(blacklist); } public async Task 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 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); } }