Skip to content

Commit

Permalink
[feat] 优化token失效后以旧换新逻辑机制
Browse files Browse the repository at this point in the history
  • Loading branch information
xianhc committed Apr 16, 2024
1 parent 75e141f commit 77476d6
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 50 deletions.
3 changes: 2 additions & 1 deletion Ape.Volo.Api/Authentication/Jwt/ITokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ public interface ITokenService
/// Issue token
/// </summary>
/// <param name="loginUserInfo"></param>
/// <param name="refresh"></param>
/// <returns></returns>
Task<Token> IssueTokenAsync(LoginUserInfo loginUserInfo);
Task<Token> IssueTokenAsync(LoginUserInfo loginUserInfo, bool refresh = false);

Task<JwtSecurityToken> ReadJwtToken(string token);
}
88 changes: 72 additions & 16 deletions Ape.Volo.Api/Authentication/Jwt/PermissionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
using Ape.Volo.Common.WebApp;
using Ape.Volo.IBusiness.Interface.Permission;
using Ape.Volo.IBusiness.Interface.System;
using IP2Region.Net.Abstractions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Shyjus.BrowserDetection;

namespace Ape.Volo.Api.Authentication.Jwt;

Expand All @@ -26,27 +28,39 @@ public class PermissionHandler : AuthorizationHandler<PermissionRequirement>

private readonly IPermissionService _permissionService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUserService _userService;
private readonly ISettingService _settingService;
private readonly JwtAuthOption _jwtOptions;
private readonly ApeContext _apeContext;
private readonly IBrowserDetector _browserDetector;
private readonly ISearcher _ipSearcher;
private readonly ITokenBlacklistService _tokenBlacklistService;

/// <summary>
/// 构造函数注入
/// 构造函数
/// </summary>
/// <param name="schemes"></param>
/// <param name="httpContextAccessor"></param>
/// <param name="permissionService"></param>
/// <param name="apeContext"></param>
/// <param name="userService"></param>
/// <param name="settingService"></param>
/// <param name="apeContext"></param>
/// <param name="browserDetector"></param>
/// <param name="searcher"></param>
/// <param name="tokenBlacklistService"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes, IHttpContextAccessor httpContextAccessor,
IPermissionService permissionService, ISettingService settingService, ApeContext apeContext)
IPermissionService permissionService, IUserService userService, ISettingService settingService,
ApeContext apeContext, IBrowserDetector browserDetector, ISearcher searcher,
ITokenBlacklistService tokenBlacklistService)
{
_httpContextAccessor = httpContextAccessor;
Schemes = schemes;
_permissionService = permissionService;
_settingService = settingService;
_apeContext = apeContext;
_jwtOptions = apeContext.Configs.JwtAuthOptions;
_userService = userService;
_browserDetector = browserDetector;
_ipSearcher = searcher;
_tokenBlacklistService = tokenBlacklistService;
}

// 重写异步处理程序
Expand Down Expand Up @@ -94,6 +108,8 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
var nowTime = DateTime.Now.ToLocalTime();
if (expTime < nowTime)
{
await _apeContext.Cache.RemoveAsync(GlobalConstants.CacheKey.OnlineKey +
_apeContext.HttpUser.JwtToken.ToMd5String16());
context.Fail();
return;
}
Expand All @@ -108,8 +124,49 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
_apeContext.HttpUser.JwtToken.ToMd5String16());
if (loginUserInfo == null)
{
context.Fail();
return;
var tokenMd5 = _apeContext.HttpUser.JwtToken.ToMd5String16();
var tokenBlacklist = await _tokenBlacklistService.TableWhere(x => x.AccessToken == tokenMd5)
.FirstAsync();
if (tokenBlacklist.IsNotNull())
{
context.Fail();
return;
}

var netUser = await _userService.QueryByIdAsync(_apeContext.HttpUser.Id);
if (netUser.IsNull())
{
context.Fail();
return;
}

var remoteIp = httpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0";
var onlineUser = new LoginUserInfo
{
UserId = netUser.Id,
Account = netUser.Username,
NickName = netUser.NickName,
DeptId = netUser.DeptId,
DeptName = netUser.Dept.Name,
Ip = remoteIp,
Address = _ipSearcher.Search(remoteIp),
OperatingSystem = _browserDetector.Browser?.OS,
DeviceType = _browserDetector.Browser?.DeviceType,
BrowserName = _browserDetector.Browser?.Name,
Version = _browserDetector.Browser?.Version,
LoginTime = DateTime.Now,
IsAdmin = netUser.IsAdmin,
AccessToken = _apeContext.HttpUser.JwtToken
};
var onlineKey = onlineUser.AccessToken.ToMd5String16();
var isTrue = await _apeContext.Cache.SetAsync(
GlobalConstants.CacheKey.OnlineKey + onlineKey, onlineUser, TimeSpan.FromHours(2),
null);
if (!isTrue)
{
context.Fail();
return;
}
}

#endregion
Expand All @@ -119,7 +176,12 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
var setting = await _settingService.FindSettingByName("IsAdminNotAuthentication");
if (setting != null && setting.Value.ToBool())
{
if (loginUserInfo.IsAdmin)
// loginUserInfo = await _apeContext.Cache.GetAsync<LoginUserInfo>(
// GlobalConstants.CacheKey.OnlineKey +
// _apeContext.HttpUser.JwtToken.ToMd5String16());
//if (loginUserInfo.IsAdmin) //不想查数据库就使用登录信息
var netUser = await _userService.QueryByIdAsync(_apeContext.HttpUser.Id);
if (netUser.IsAdmin)
{
context.Succeed(requirement);
return;
Expand Down Expand Up @@ -238,14 +300,8 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
}
}

//判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败
if (requestPath != null &&
!requestPath.Equals(_jwtOptions.LoginPath.ToLower(), StringComparison.Ordinal) &&
(!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
{
context.Fail();
return;
}
context.Fail();
return;
}

context.Fail();
Expand Down
17 changes: 13 additions & 4 deletions Ape.Volo.Api/Authentication/Jwt/TokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public TokenService(IOptionsMonitor<Configs> configs)
_jwtOptions = (configs?.CurrentValue ?? new Configs()).JwtAuthOptions;
}

public async Task<Token> IssueTokenAsync(LoginUserInfo loginUserInfo)
public async Task<Token> IssueTokenAsync(LoginUserInfo loginUserInfo, bool refresh = false)
{
if (loginUserInfo == null)
throw new ArgumentNullException(nameof(loginUserInfo));
Expand All @@ -49,20 +49,29 @@ public async Task<Token> IssueTokenAsync(LoginUserInfo loginUserInfo)
audience: _jwtOptions.Audience,
claims: cls,
notBefore: nowTime,
expires: nowTime.AddSeconds(_jwtOptions.Expires),
expires: nowTime.AddHours(_jwtOptions.Expires),
signingCredentials: signinCredentials
);


var token = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
if (refresh)
{
return await Task.FromResult(new Token()
{
Expires = _jwtOptions.Expires * 3600,
TokenType = AuthConstants.JwtTokenType,
RefreshToken = token,
});
}

return await Task.FromResult(new Token()
{
AccessToken = token,
Expires = _jwtOptions.Expires,
Expires = _jwtOptions.Expires * 3600,
TokenType = AuthConstants.JwtTokenType,
RefreshToken = "",
RefreshTokenExpires = _jwtOptions.RefreshTokenExpires
RefreshTokenExpires = _jwtOptions.RefreshTokenExpires * 3600
});
}

Expand Down
54 changes: 34 additions & 20 deletions Ape.Volo.Api/Controllers/Auth/AuthorizationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Ape.Volo.IBusiness.Dto.Permission;
using Ape.Volo.IBusiness.Interface.Permission;
using Ape.Volo.IBusiness.Interface.Queued;
using Ape.Volo.IBusiness.Interface.System;
using Ape.Volo.IBusiness.RequestModel;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -38,21 +39,23 @@ public class AuthorizationController : BaseApiController
private readonly IQueuedEmailService _queuedEmailService;
private readonly ApeContext _apeContext;
private readonly ITokenService _tokenService;
private readonly ITokenBlacklistService _tokenBlacklistService;

#endregion

#region 构造函数

public AuthorizationController(IUserService userService, IPermissionService permissionService,
IOnlineUserService onlineUserService, IQueuedEmailService queuedEmailService, ApeContext apeContext,
ITokenService tokenService)
ITokenService tokenService, ITokenBlacklistService tokenBlacklistService)
{
_userService = userService;
_permissionService = permissionService;
_onlineUserService = onlineUserService;
_queuedEmailService = queuedEmailService;
_apeContext = apeContext;
_tokenService = tokenService;
_tokenBlacklistService = tokenBlacklistService;
}

#endregion
Expand Down Expand Up @@ -117,27 +120,32 @@ public async Task<ActionResult<object>> RefreshToken(string token = "")
{
if (token.IsNullOrEmpty())
{
return Error("token已丢弃,请重新登录!");
return Error("token已丢失,请重新登录!");
}

var jwtSecurityToken = await _tokenService.ReadJwtToken(token);
if (jwtSecurityToken != null)
var tokenMd5 = token.ToMd5String16();
var tokenBlacklist = await _tokenBlacklistService.TableWhere(x => x.AccessToken == tokenMd5).FirstAsync();
if (tokenBlacklist.IsNull())
{
var userId = Convert.ToInt64(jwtSecurityToken.Claims
.FirstOrDefault(s => s.Type == AuthConstants.JwtClaimTypes.Jti)?.Value);
var loginTime = Convert.ToInt64(jwtSecurityToken.Claims
.FirstOrDefault(s => s.Type == AuthConstants.JwtClaimTypes.Iat)?.Value).TicksToDateTime();
var nowTime = DateTime.Now.ToLocalTime();
var refreshTime = loginTime.AddSeconds(_apeContext.Configs.JwtAuthOptions.RefreshTokenExpires);
// 允许token刷新时间内
if (nowTime <= refreshTime)
var jwtSecurityToken = await _tokenService.ReadJwtToken(token);
if (jwtSecurityToken != null)
{
var netUser = await _userService.QueryByIdAsync(userId);
if (netUser.IsNotNull())
var userId = Convert.ToInt64(jwtSecurityToken.Claims
.FirstOrDefault(s => s.Type == AuthConstants.JwtClaimTypes.Jti)?.Value);
var loginTime = Convert.ToInt64(jwtSecurityToken.Claims
.FirstOrDefault(s => s.Type == AuthConstants.JwtClaimTypes.Iat)?.Value).TicksToDateTime();
var nowTime = DateTime.Now.ToLocalTime();
var refreshTime = loginTime.AddHours(_apeContext.Configs.JwtAuthOptions.RefreshTokenExpires);
// 允许token刷新时间内
if (nowTime <= refreshTime)
{
if (netUser.UpdateTime == null || netUser.UpdateTime < loginTime)
var netUser = await _userService.QueryByIdAsync(userId);
if (netUser.IsNotNull())
{
return await LoginResult(netUser, "refresh");
if (netUser.UpdateTime == null || netUser.UpdateTime < loginTime)
{
return await LoginResult(netUser, "refresh");
}
}
}
}
Expand Down Expand Up @@ -229,14 +237,20 @@ await _apeContext.Cache.RemoveAsync(
/// <returns></returns>
private async Task<string> LoginResult(UserDto userDto, string type)
{
var permissionRoles = await _permissionService.GetPermissionRolesAsync(userDto.Id);
permissionRoles.AddRange(userDto.Roles.Select(r => r.Permission));
var permissionRoles = new List<string>();
bool refresh = true;
if (type.Equals("login"))
{
refresh = false;
permissionRoles = await _permissionService.GetPermissionRolesAsync(userDto.Id);
permissionRoles.AddRange(userDto.Roles.Select(r => r.Permission));
}

var remoteIp = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0";
var jwtUserVo = await _onlineUserService.CreateJwtUserAsync(userDto, permissionRoles);
var loginUserInfo = await _onlineUserService.SaveLoginUserAsync(jwtUserVo, remoteIp);
var token = await _tokenService.IssueTokenAsync(loginUserInfo);
loginUserInfo.AccessToken = token.AccessToken;
var token = await _tokenService.IssueTokenAsync(loginUserInfo, refresh);
loginUserInfo.AccessToken = refresh ? token.RefreshToken : token.AccessToken;
var onlineKey = loginUserInfo.AccessToken.ToMd5String16();
await _apeContext.Cache.SetAsync(
GlobalConstants.CacheKey.OnlineKey + onlineKey,
Expand Down
4 changes: 2 additions & 2 deletions Ape.Volo.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"Audience": "http://localhost",
"Issuer": "http://localhost",
"SecurityKey": "5ixKD0BkJxYYroZTvdPs3w9NWRoiUacN",
"Expires": 7200,
"RefreshTokenExpires": 172800,
"Expires": 12,
"RefreshTokenExpires": 168,
"LoginPath": "/auth/login"
},
"IsInitTable": true,
Expand Down
4 changes: 2 additions & 2 deletions Ape.Volo.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"Audience": "http://localhost",
"Issuer": "http://localhost",
"SecurityKey": "5ixKD0BkJxYYroZTvdPs3w9NWRoiUacN",
"Expires": 7200,
"RefreshTokenExpires": 172800,
"Expires": 12,
"RefreshTokenExpires": 168,
"LoginPath": "/auth/login"
},
"IsInitTable": true,
Expand Down
15 changes: 12 additions & 3 deletions Ape.Volo.Business/Monitor/OnlineUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
using Ape.Volo.Common.Global;
using Ape.Volo.Common.Model;
using Ape.Volo.Common.WebApp;
using Ape.Volo.Entity.System;
using Ape.Volo.IBusiness.ExportModel.Monitor;
using Ape.Volo.IBusiness.Interface.Monitor;
using Ape.Volo.IBusiness.Interface.System;

namespace Ape.Volo.Business.Monitor;

public class OnlineUserService : IOnlineUserService
{
private readonly ICache _cache;
private readonly ITokenBlacklistService _tokenBlacklistService;

public OnlineUserService(ICache cache)
public OnlineUserService(ICache cache, ITokenBlacklistService tokenBlacklistService)
{
_cache = cache;
_tokenBlacklistService = tokenBlacklistService;
}

public async Task<List<LoginUserInfo>> QueryAsync(Pagination pagination)
Expand Down Expand Up @@ -49,9 +53,14 @@ public async Task<List<LoginUserInfo>> QueryAsync(Pagination pagination)

public async Task DropOutAsync(HashSet<string> ids)
{
foreach (var item in ids)
var list = new List<TokenBlacklist>();
list.AddRange(ids.Select(x => new TokenBlacklist() { AccessToken = x }));
if (await _tokenBlacklistService.AddEntityListAsync(list))
{
await _cache.RemoveAsync(GlobalConstants.CacheKey.OnlineKey + item);
foreach (var item in ids)
{
await _cache.RemoveAsync(GlobalConstants.CacheKey.OnlineKey + item);
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions Ape.Volo.Business/System/TokenBlacklistService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Ape.Volo.Business.Base;
using Ape.Volo.Common.WebApp;
using Ape.Volo.Entity.System;
using Ape.Volo.IBusiness.Interface.System;

namespace Ape.Volo.Business.System;

/// <summary>
/// Token黑名单
/// </summary>
public class TokenBlacklistService : BaseServices<TokenBlacklist>, ITokenBlacklistService
{
public TokenBlacklistService(ApeContext apeContext) : base(apeContext)
{
}
}
4 changes: 2 additions & 2 deletions Ape.Volo.Common/ConfigOptions/Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ public JwtAuthOption JwtAuthOptions
Audience = "http://localhost",
Issuer = "http://localhost",
SecurityKey = "5ixKD0BkJxYYroZTvdPs3w9NWRoiUacN",
Expires = 3600,
RefreshTokenExpires = 86400,
Expires = 12,
RefreshTokenExpires = 168,
LoginPath = "/auth/login"
};
}
Expand Down
Loading

0 comments on commit 77476d6

Please sign in to comment.