Merge pull request '🍒 feat(auth): 实现 session 自动刷新;解决跨用户操作时会话信息不一致的问题;使用服务器时间比较token的有效期' (#430) from jasondom/Admin.NET.Pro:v2-1 into v2
Reviewed-on: https://code.adminnet.top/Admin.NET/Admin.NET.Pro/pulls/430
This commit is contained in:
commit
d3d07e7b73
@ -440,4 +440,15 @@ public class SysAuthService : IDynamicApiController, ITransient
|
|||||||
return 401;
|
return 401;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 刷新token
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
[NonAction]
|
||||||
|
public async Task RefreshToken(long userId)
|
||||||
|
{
|
||||||
|
var user = await _sysUserRep.AsQueryable().IgnoreTenant().Includes(u => u.SysOrg).FirstAsync(u => u.Id == userId);
|
||||||
|
await CreateToken(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -457,6 +457,17 @@ public class SysCacheService : IDynamicApiController, ISingleton
|
|||||||
return _cacheProvider.Cache.ContainsKey($"{_cacheOptions.Prefix}{key}");
|
return _cacheProvider.Cache.ContainsKey($"{_cacheOptions.Prefix}{key}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查缓存是否不存在
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">键</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[NonAction]
|
||||||
|
public bool NotExistKey(string key)
|
||||||
|
{
|
||||||
|
return !_cacheProvider.Cache.ContainsKey($"{_cacheOptions.Prefix}{key}");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据键名前缀删除缓存 🔖
|
/// 根据键名前缀删除缓存 🔖
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -168,6 +168,9 @@ public class SysUserService : IDynamicApiController, ITransient
|
|||||||
// 更新域账号
|
// 更新域账号
|
||||||
await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount);
|
await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount);
|
||||||
|
|
||||||
|
// 清除用户session
|
||||||
|
_userManager.RemoveSession(input.Id);
|
||||||
|
|
||||||
// 发布更新用户事件
|
// 发布更新用户事件
|
||||||
await _eventPublisher.PublishAsync(UserEventTypeEnum.Update, input);
|
await _eventPublisher.PublishAsync(UserEventTypeEnum.Update, input);
|
||||||
}
|
}
|
||||||
@ -246,8 +249,11 @@ public class SysUserService : IDynamicApiController, ITransient
|
|||||||
[DisplayName("更新用户基本信息")]
|
[DisplayName("更新用户基本信息")]
|
||||||
public virtual async Task<int> UpdateBaseInfo(SysUser user)
|
public virtual async Task<int> UpdateBaseInfo(SysUser user)
|
||||||
{
|
{
|
||||||
return await _sysUserRep.AsUpdateable(user)
|
var count = await _sysUserRep.AsUpdateable(user)
|
||||||
.IgnoreColumns(u => new { u.CreateTime, u.Account, u.Password, u.AccountType, u.OrgId, u.PosId }).ExecuteCommandAsync();
|
.IgnoreColumns(u => new { u.CreateTime, u.Account, u.Password, u.AccountType, u.OrgId, u.PosId }).ExecuteCommandAsync();
|
||||||
|
// 清除用户session
|
||||||
|
_userManager.RemoveSession(user.Id);
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -492,5 +498,8 @@ public class SysUserService : IDynamicApiController, ITransient
|
|||||||
|
|
||||||
// 强制下线账号
|
// 强制下线账号
|
||||||
await _sysOnlineUserService.ForceOfflineByUserId(user.Id);
|
await _sysOnlineUserService.ForceOfflineByUserId(user.Id);
|
||||||
|
|
||||||
|
// 清除用户session
|
||||||
|
_userManager.RemoveSession(user.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,7 +27,14 @@ public class UserManager(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
[Newtonsoft.Json.JsonIgnore]
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
protected virtual UserSessionDao Session => _session ??= sysCacheService.Get<UserSessionDao>(CacheConst.KeyUserSession + UserId);
|
protected virtual UserSessionDao Session
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_session == null || _session.UserId != UserId) _session = sysCacheService.Get<UserSessionDao>(CacheConst.KeyUserSession + UserId);
|
||||||
|
return _session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户Id
|
/// 用户Id
|
||||||
|
|||||||
@ -62,7 +62,7 @@ namespace Admin.NET.Web.Core
|
|||||||
// 验证Token黑名单
|
// 验证Token黑名单
|
||||||
var userId = httpContext.User.FindFirst(ClaimConst.UserId)?.Value;
|
var userId = httpContext.User.FindFirst(ClaimConst.UserId)?.Value;
|
||||||
var version = httpContext.User.FindFirst(ClaimConst.TokenVersion)?.Value;
|
var version = httpContext.User.FindFirst(ClaimConst.TokenVersion)?.Value;
|
||||||
if (sysCacheService.ExistKey($"{CacheConst.KeyTokenBlacklist}{userId}:{version}") || !sysCacheService.ExistKey($"{CacheConst.KeyUserSession}{userId}"))
|
if (sysCacheService.ExistKey($"{CacheConst.KeyTokenBlacklist}{userId}:{version}"))
|
||||||
{
|
{
|
||||||
context.Fail(new AuthorizationFailureReason(this, "令牌已失效,请重新登录。"));
|
context.Fail(new AuthorizationFailureReason(this, "令牌已失效,请重新登录。"));
|
||||||
context.StatusCode(StatusCodes.Status401Unauthorized);
|
context.StatusCode(StatusCodes.Status401Unauthorized);
|
||||||
@ -70,6 +70,14 @@ namespace Admin.NET.Web.Core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新 Session
|
||||||
|
if (sysCacheService.NotExistKey($"{CacheConst.KeyUserSession}{userId}"))
|
||||||
|
{
|
||||||
|
var sysAuthService = serviceScope.ServiceProvider.GetRequiredService<SysAuthService>();
|
||||||
|
await sysAuthService.RefreshToken(long.Parse(userId!));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(userId))
|
if (!string.IsNullOrWhiteSpace(userId))
|
||||||
{
|
{
|
||||||
// 查库并缓存用户Token版本
|
// 查库并缓存用户Token版本
|
||||||
|
|||||||
1
Web/src/types/pinia.d.ts
vendored
1
Web/src/types/pinia.d.ts
vendored
@ -46,6 +46,7 @@ declare interface RoutesListState<T = any> {
|
|||||||
// 布局配置
|
// 布局配置
|
||||||
declare interface ThemeConfigState {
|
declare interface ThemeConfigState {
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
serverTime?: Date; // 服务器时间
|
||||||
isDrawer: boolean; // 是否开启抽屉配置
|
isDrawer: boolean; // 是否开启抽屉配置
|
||||||
primary: string; // 主题颜色
|
primary: string; // 主题颜色
|
||||||
topBar: string; // 顶部栏背景
|
topBar: string; // 顶部栏背景
|
||||||
|
|||||||
@ -11,6 +11,11 @@ import { BaseAPI, BASE_PATH } from '../api-services/system/base';
|
|||||||
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { Local, Session } from '../utils/storage';
|
import { Local, Session } from '../utils/storage';
|
||||||
|
import {useThemeConfig} from "/@/stores/themeConfig";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
|
||||||
|
const storesThemeConfig = useThemeConfig();
|
||||||
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||||
|
|
||||||
// 接口服务器配置
|
// 接口服务器配置
|
||||||
export const serveConfig = new Configuration({
|
export const serveConfig = new Configuration({
|
||||||
@ -66,7 +71,7 @@ axiosInstance.interceptors.request.use(
|
|||||||
const exp = getJWTDate(jwt.exp as number);
|
const exp = getJWTDate(jwt.exp as number);
|
||||||
|
|
||||||
// token 已经过期
|
// token 已经过期
|
||||||
if (new Date() >= exp) {
|
if (themeConfig.value.serverTime && themeConfig.value.serverTime >= exp) {
|
||||||
// 获取刷新 token
|
// 获取刷新 token
|
||||||
const refreshAccessToken = Local.get(refreshAccessTokenKey);
|
const refreshAccessToken = Local.get(refreshAccessTokenKey);
|
||||||
|
|
||||||
@ -103,6 +108,9 @@ axiosInstance.interceptors.request.use(
|
|||||||
// axios 响应拦截
|
// axios 响应拦截
|
||||||
axiosInstance.interceptors.response.use(
|
axiosInstance.interceptors.response.use(
|
||||||
(res) => {
|
(res) => {
|
||||||
|
// 记录服务器时间
|
||||||
|
themeConfig.value.serverTime = res.data.time ? new Date(res.data.time) : undefined;
|
||||||
|
|
||||||
// 获取状态码和返回数据
|
// 获取状态码和返回数据
|
||||||
var status = res.status;
|
var status = res.status;
|
||||||
var serve = res.data;
|
var serve = res.data;
|
||||||
@ -241,4 +249,4 @@ export function getJWTDate(timestamp: number): Date {
|
|||||||
*/
|
*/
|
||||||
export function sleep(delay: number) {
|
export function sleep(delay: number) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, delay));
|
return new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
}
|
}
|
||||||
@ -65,6 +65,8 @@ export async function loadSysInfo(tenantid: number) {
|
|||||||
themeConfig.value.onlineNotice = data.onlineNotice;
|
themeConfig.value.onlineNotice = data.onlineNotice;
|
||||||
// 密码加解密公匙
|
// 密码加解密公匙
|
||||||
window.__env__.VITE_SM_PUBLIC_KEY = data.publicKey;
|
window.__env__.VITE_SM_PUBLIC_KEY = data.publicKey;
|
||||||
|
// 服务器时间
|
||||||
|
themeConfig.value.serverTime = res.data.time ? new Date(Date.parse(res.data.time as any)) : undefined;
|
||||||
|
|
||||||
// 更新 favicon
|
// 更新 favicon
|
||||||
updateFavicon(data.logo);
|
updateFavicon(data.logo);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user