diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Options/JwtOptions.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Options/JwtOptions.cs index bc819cd5..0b3ed531 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Options/JwtOptions.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Options/JwtOptions.cs @@ -14,6 +14,6 @@ namespace Yi.Framework.Rbac.Domain.Shared.Options public string SecurityKey { get; set; } = "892u4j1803qj23jro0fjkf8bmsdf9nb9mf92834u23jdf923jrnmvasbceqwt347562tgdhdnsv9wevbnop"; - public long ExpiresMinuteTime { get; set; } = 120; + public long ExpiresSecondTime { get; set; } = 600; } } diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Authorization/RefreshTokenMiddleware.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Authorization/RefreshTokenMiddleware.cs index cdd48832..73a8fd1a 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Authorization/RefreshTokenMiddleware.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Authorization/RefreshTokenMiddleware.cs @@ -19,9 +19,9 @@ namespace Yi.Framework.Rbac.Domain.Authorization public class RefreshTokenMiddleware : IMiddleware, ITransientDependency { private AccountManager _accountManager; + public RefreshTokenMiddleware(AccountManager accountManager) { - _accountManager = accountManager; } @@ -30,22 +30,34 @@ namespace Yi.Framework.Rbac.Domain.Authorization var refreshToken = context.Request.Headers["refresh_token"].ToString(); if (!string.IsNullOrEmpty(refreshToken)) { - //每个用户的token刷新频率可以进行控制,防止刷新token当访问token使用 - var authResult = await context.AuthenticateAsync(TokenTypeConst.Refresh); - //token鉴权刷新成功 - if (authResult.Succeeded) + //Jwt鉴权失败,过期了,再去找刷新token,进行刷新处理,不用每次都去刷新 + var bearerAuthResult = await context.AuthenticateAsync("Bearer"); + if (!bearerAuthResult.Succeeded) { - var userId = Guid.Parse(authResult.Principal.FindFirst(AbpClaimTypes.UserId).Value.ToString()); - var access_Token = await _accountManager.GetTokenByUserIdAsync(userId); - var refresh_Token = _accountManager.CreateRefreshToken(userId); - context.Response.Headers["access_token"] = access_Token; - context.Response.Headers["refresh_token"] = refresh_Token; + //每个用户的token刷新频率可以进行控制,防止刷新token当访问token使用 + var authResult = await context.AuthenticateAsync(TokenTypeConst.Refresh); + //token鉴权刷新成功 + if (authResult.Succeeded) + { + var userId = Guid.Parse(authResult.Principal.FindFirst(AbpClaimTypes.UserId).Value.ToString()); + var access_Token = await _accountManager.GetTokenByUserIdAsync(userId); + var refresh_Token = _accountManager.CreateRefreshToken(userId); + context.Response.Headers["access_token"] = access_Token; + context.Response.Headers["refresh_token"] = refresh_Token; - //请求头替换,补充后续鉴权逻辑 - context.Request.Headers["Authorization"] = "Bearer " + access_Token; + //请求头替换,补充后续鉴权逻辑 + context.Request.Headers["Authorization"] = "Bearer " + access_Token; + } + //刷新token 与 access_token都失效了 + // else + // { + //context.Response.StatusCode = StatusCodes.Status401Unauthorized; + //return; + // } } } + await next(context); } } @@ -57,8 +69,6 @@ namespace Yi.Framework.Rbac.Domain.Authorization { app.UseMiddleware(); return app; - } } - -} +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs index ca933563..28dc9de5 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs @@ -106,7 +106,7 @@ namespace Yi.Framework.Rbac.Domain.Managers issuer: _jwtOptions.Issuer, audience: _jwtOptions.Audience, claims: claims, - expires: DateTime.Now.AddMinutes(_jwtOptions.ExpiresMinuteTime), + expires: DateTime.Now.AddSeconds(_jwtOptions.ExpiresSecondTime), notBefore: DateTime.Now, signingCredentials: creds); string returnToken = new JwtSecurityTokenHandler().WriteToken(token); @@ -127,7 +127,7 @@ namespace Yi.Framework.Rbac.Domain.Managers issuer: _refreshJwtOptions.Issuer, audience: _refreshJwtOptions.Audience, claims: claims, - expires: DateTime.Now.AddMinutes(_refreshJwtOptions.ExpiresMinuteTime), + expires: DateTime.Now.AddSeconds(_refreshJwtOptions.ExpiresSecondTime), notBefore: DateTime.Now, signingCredentials: creds); string returnToken = new JwtSecurityTokenHandler().WriteToken(token); diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/UserManager.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/UserManager.cs index 5bcf9ce9..46ace5d8 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/UserManager.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/UserManager.cs @@ -190,25 +190,21 @@ namespace Yi.Framework.Rbac.Domain.Managers { //此处优先从缓存中获取 UserRoleMenuDto output = null; - var tokenExpiresMinuteTime = - LazyServiceProvider.GetRequiredService>().Value.ExpiresMinuteTime; + var tokenExpiresSecondTime = + LazyServiceProvider.GetRequiredService>().Value.ExpiresSecondTime; var cacheData = await _userCache.GetOrAddAsync(new UserInfoCacheKey(userId), async () => { var user = await _userRepository.GetUserAllInfoAsync(userId); var data = EntityMapToDto(user); //系统用户数据被重置,老前端访问重新授权 - if (data is null) - { - throw new AbpAuthorizationException(); - } //data.Menus.Clear(); - output = data; + output = data ?? throw new AbpAuthorizationException(); return new UserInfoCacheItem(data); }, () => new DistributedCacheEntryOptions - { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(tokenExpiresMinuteTime) }); + { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(tokenExpiresSecondTime) }); if (cacheData is not null) { diff --git a/Yi.Bbs.Vue3/src/config/axios/service.js b/Yi.Bbs.Vue3/src/config/axios/service.js index 24efd609..876636c4 100644 --- a/Yi.Bbs.Vue3/src/config/axios/service.js +++ b/Yi.Bbs.Vue3/src/config/axios/service.js @@ -1,99 +1,117 @@ import axios from "axios"; import router from "@/router"; -import { ElMessage } from "element-plus"; -import { config } from "@/config/axios/config"; -import { Session } from "@/utils/storage"; +import {ElMessage} from "element-plus"; +import {config} from "@/config/axios/config"; +import {Session} from "@/utils/storage"; import useAuths from "@/hooks/useAuths"; -const { VITE_APP_ENV_NAME } = import.meta.env; -const { getToken, removeToken } = useAuths(); +const {VITE_APP_ENV_NAME} = import.meta.env; +const {getToken,getRefreshToken, removeToken, setToken, setRefreshToken} = useAuths(); -const { base_url, request_timeout, pre_interface } = config; +const {base_url, request_timeout, pre_interface} = config; export const PATH_URL = base_url[VITE_APP_ENV_NAME]; // 配置新建一个 axios 实例 const service = axios.create({ - baseURL: PATH_URL, // api 的 base_url - timeout: request_timeout, // 请求超时时间 - headers: { "Content-Type": "application/json" }, - hideerror: false, //是否在底层显示错误信息 - isFinish: false, + baseURL: PATH_URL, // api 的 base_url + timeout: request_timeout, // 请求超时时间 + headers: {"Content-Type": "application/json"}, + hideerror: false, //是否在底层显示错误信息 + isFinish: false, }); // 添加请求拦截器 service.interceptors.request.use( - (config) => { - // 在发送请求之前做些什么 token - const token = getToken(); - if (token) { - config.headers["Authorization"] = `Bearer ${token}`; + (config) => { + // 在发送请求之前做些什么 token + const token = getToken(); + if (token) { + config.headers["Authorization"] = `Bearer ${token}`; + } + const refreshToken = getRefreshToken(); + if (refreshToken) { + config.headers["refresh_token"] = `${refreshToken}`; + } + + if (Session.get("tenantId")) { + config.headers["TenantId"] = Session.get("tenantId"); + } + return config; + }, + (error) => { + // 对请求错误做些什么 + return Promise.reject(error); } - if (Session.get("tenantId")) { - config.headers["TenantId"] = Session.get("tenantId"); - } - return config; - }, - (error) => { - // 对请求错误做些什么 - return Promise.reject(error); - } ); // 添加响应拦截器 service.interceptors.response.use( - (response) => { - const { config } = response; - config.isFinish = true; - return Promise.resolve(response); - }, - (error) => { - const { config } = error; - // 对响应错误做点什么 - if (error.message.indexOf("timeout") != -1) { - ElMessage({ - type: "error", - message: "网络超时", - }); - } else if (error.message == "Network Error") { - ElMessage({ - type: "error", - message: "网络连接错误", - }); - } else { - const res = error.response || {}; - const status = Number(res.status) || 200; - const message = res?.data?.error?.message; - if (status === 401) { - ElMessageBox.confirm("该功能需要登陆后享有,是否立即登录?", "提示", { - confirmButtonText: "确认", - cancelButtonText: "取消", - type: "warning", - }).then(() => { - removeToken(); - router.push("/login"); - }); - return; - } - if (status !== 200) { - if (status >= 500) { - ElMessage({ - type: "error", - message: "网络开小差了,请稍后再试", - }); - return Promise.reject(new Error(message)); + (response) => { + const {config} = response; + config.isFinish = true; + //后端返回双token,替换 + if (response.headers["refresh_token"]) { + setToken(response.headers["access_token"]); + setRefreshToken(response.headers["refresh_token"]); + // //然后修改config重新请求 + // config.headers["Authorization"] = `Bearer ${getToken()}`; + // service.request(config); } - // 避开找不到后端接口的提醒 - if (status !== 404) { - ElMessage({ - type: "error", - message, - }); + + return Promise.resolve(response); + }, + (error) => { + const {config} = error; + // 对响应错误做点什么 + if (error.message.indexOf("timeout") !== -1) { + ElMessage({ + type: "error", + message: "网络超时", + }); + } else if (error.message === "Network Error") { + ElMessage({ + type: "error", + message: "网络连接错误", + }); + } else { + + //处理状态码和消息 + const res = error.response || {}; + const status = Number(res.status) || 200; + const message = res?.data?.error?.message; + + if (status === 401) { + ElMessageBox.confirm("该功能需要登陆后享有,是否立即登录?", "提示", { + confirmButtonText: "确认", + cancelButtonText: "取消", + type: "warning", + }).then(() => { + removeToken(); + router.push("/login"); + }); + return; + } + //处理非200 + if (status !== 200) { + if (status >= 500) { + ElMessage({ + type: "error", + message: "网络开小差了,请稍后再试", + }); + return Promise.reject(new Error(message)); + } + // 避开找不到后端接口的提醒 + if (status !== 404) { + ElMessage({ + type: "error", + message, + }); + } + } } - } + config.isFinish = true; + return Promise.reject(error.response); } - config.isFinish = true; - return Promise.reject(error.response); - } ); // 导出 axios 实例 diff --git a/Yi.Bbs.Vue3/src/hooks/useAuths.js b/Yi.Bbs.Vue3/src/hooks/useAuths.js index 6b40986e..6e227ae2 100644 --- a/Yi.Bbs.Vue3/src/hooks/useAuths.js +++ b/Yi.Bbs.Vue3/src/hooks/useAuths.js @@ -14,213 +14,210 @@ export const AUTH_USER = "AUTH_USER"; export default function useAuths(opt) { + const defaultOpt = { + loginUrl: "/login", // 登录页跳转url 默认: /login + loginReUrl: "", // 登录页登陆成功后带重定向redirect=的跳转url 默认为空 + homeUrl: "/index", // 主页跳转url 默认: /index + otherQuery: {}, // 成功登录后携带的(除redirect外)其他参数 + }; - const defaultOpt = { - loginUrl: "/login", // 登录页跳转url 默认: /login - loginReUrl: "", // 登录页登陆成功后带重定向redirect=的跳转url 默认为空 - homeUrl: "/index", // 主页跳转url 默认: /index - otherQuery: {}, // 成功登录后携带的(除redirect外)其他参数 - }; + let option = { + ...defaultOpt, + ...opt, + }; - let option = { - ...defaultOpt, - ...opt, - }; + // 获取token + const getToken = () => { + var token = Local.get(TokenKey); + return token; + }; + // 获取token + const getRefreshToken = () => { + return Local.get(RefreshTokenKey); + }; - // 获取token - const getToken = () => { - var token= Local.get(TokenKey); - return token; - }; - // 获取token - const getRefreshToken = () => { - return Local.get(RefreshTokenKey); - }; - -const isLogin=computed(()=>{ - return getToken()? true : false -}); - -const currentUserInfo=computed(()=>{ - return useUserStore(); -}); - - // 存储token到cookies - const setToken = (token) => { - if (token == null) { - return false; - } - Local.set(TokenKey, token); - - return true; - }; - // 存储RefreshToken到cookies - const setRefreshToken = (token) => { - if (token == null) { - return false; - } - Local.set(RefreshTokenKey, token); - - return true; - }; - - // 退出登录 - const logoutFun = async () => { - let flag = true; - try { - await userLogout().then((res) => { - useUserStore().updateToken(null); - ElMessage({ - message: "退出成功", - type: "info", - duration: 2000, - }); - }); - } catch (error) { - flag = await ElMessageBox.confirm( - `退出登录失败,是否强制退出?`, - "提示", - { - confirmButtonText: "确 定", - cancelButtonText: "取 消", - type: "warning", - } - ) - .then(() => { - useUserStore().updateToken(null); - return true; - }) - .catch(() => { - //取消 - return false; - }); - } - - if (flag) { - clearStorage(); - } - }; - - // 清空本地存储的信息 - const clearStorage = () => { - Session.clear(); - Local.clear(); - removeToken(); - - }; - - // 用户名密码登录 - const loginFun = async (params) => { - try { - const res = await userLogin(params); - - ElMessage({ - message: `您好${params.userName},登录成功!`, - type: "success", - }); - await loginSuccess(res); - return res; - } catch (error) { - const { data } = error; - if (error.status === 403 && data.error?.message === "验证码错误") { - useUserStore().updateCodeImage(); - } - } - }; - - // 获取用户基本信息、角色、菜单权限 - const getUserInfo = async () => { - try { - let { data } = await getUserDetailInfo(); - // useUserStore - // store.dispatch("updateUserInfo", result); - return data; - } catch (error) { - return {}; - } - }; - - - // 删除token - const removeToken = () => { - // console.log("token发生改变22清除清除") - Local.remove(TokenKey); - return true; - }; - - - // 登录成功之后的操作 - const loginSuccess = async (res) => { - const { token,refreshToken } = res.data; - - setToken(token); - setRefreshToken(refreshToken); - useUserStore().updateToken(token); - try { - // 存储用户信息 - - await useUserStore().getInfo(); // 用户信息 - // 登录成功后 路由跳转 - // 如果有记录当前跳转页面 - const currentPath = Session.get("currentPath"); - if (currentPath) { - router.replace(currentPath); - } else { - router.replace({ - path: option.loginReUrl ? option.loginReUrl : option.homeUrl, - query: option.otherQuery, - }); - } - } catch (error) { - removeToken(); - return false; - } - }; - - - - - // 注册 - const registerFun = async (params) => { - // try { - await userRegister(params); - ElMessage({ - message: `恭喜!${params.userName},注册成功!请登录!`, - type: "success", - }); - // } catch (error) { - // console.log(error); - // } - }; - - // 找回密码 - const retrievePasswordFun = async (params) => { - // try { - const {data}=await userRetrievePassword(params); - ElMessage({ - message: `恭喜!账号:${data},找回成功!密码已重置,请登录!`, - type: "success", - duration: 8000 + const isLogin = computed(() => { + return getToken() ? true : false }); - // } catch (error) { - // console.log(error); - // } - }; - return { - getToken, - getRefreshToken, - setToken, - setRefreshToken, - removeToken, - loginFun, - getUserInfo, - logoutFun, - retrievePasswordFun, - clearStorage, - registerFun, - loginSuccess, - isLogin, - currentUserInfo - }; + const currentUserInfo = computed(() => { + return useUserStore(); + }); + + // 存储token到cookies + const setToken = (token) => { + if (token == null) { + return false; + } + Local.set(TokenKey, token); + + return true; + }; + // 存储RefreshToken到cookies + const setRefreshToken = (token) => { + if (token == null) { + return false; + } + Local.set(RefreshTokenKey, token); + + return true; + }; + + // 退出登录 + const logoutFun = async () => { + let flag = true; + try { + await userLogout().then((res) => { + useUserStore().updateToken(null); + ElMessage({ + message: "退出成功", + type: "info", + duration: 2000, + }); + }); + } catch (error) { + flag = await ElMessageBox.confirm( + `退出登录失败,是否强制退出?`, + "提示", + { + confirmButtonText: "确 定", + cancelButtonText: "取 消", + type: "warning", + } + ) + .then(() => { + useUserStore().updateToken(null); + return true; + }) + .catch(() => { + //取消 + return false; + }); + } + + if (flag) { + clearStorage(); + } + }; + + // 清空本地存储的信息 + const clearStorage = () => { + Session.clear(); + Local.clear(); + removeToken(); + + }; + + // 用户名密码登录 + const loginFun = async (params) => { + try { + const res = await userLogin(params); + + ElMessage({ + message: `您好${params.userName},登录成功!`, + type: "success", + }); + await loginSuccess(res); + return res; + } catch (error) { + const {data} = error; + if (error.status === 403 && data.error?.message === "验证码错误") { + useUserStore().updateCodeImage(); + } + } + }; + + // 获取用户基本信息、角色、菜单权限 + const getUserInfo = async () => { + try { + let {data} = await getUserDetailInfo(); + // useUserStore + // store.dispatch("updateUserInfo", result); + return data; + } catch (error) { + return {}; + } + }; + + + // 删除token + const removeToken = () => { + // console.log("token发生改变22清除清除") + Local.remove(TokenKey); + return true; + }; + + + // 登录成功之后的操作 + const loginSuccess = async (res) => { + const {token, refreshToken} = res.data; + + setToken(token); + setRefreshToken(refreshToken); + useUserStore().updateToken(token); + try { + // 存储用户信息 + + await useUserStore().getInfo(); // 用户信息 + // 登录成功后 路由跳转 + // 如果有记录当前跳转页面 + const currentPath = Session.get("currentPath"); + if (currentPath) { + router.replace(currentPath); + } else { + router.replace({ + path: option.loginReUrl ? option.loginReUrl : option.homeUrl, + query: option.otherQuery, + }); + } + } catch (error) { + removeToken(); + return false; + } + }; + + + // 注册 + const registerFun = async (params) => { + // try { + await userRegister(params); + ElMessage({ + message: `恭喜!${params.userName},注册成功!请登录!`, + type: "success", + }); + // } catch (error) { + // console.log(error); + // } + }; + + // 找回密码 + const retrievePasswordFun = async (params) => { + // try { + const {data} = await userRetrievePassword(params); + ElMessage({ + message: `恭喜!账号:${data},找回成功!密码已重置,请登录!`, + type: "success", + duration: 8000 + }); + // } catch (error) { + // console.log(error); + // } + }; + + return { + getToken, + getRefreshToken, + setToken, + setRefreshToken, + removeToken, + loginFun, + getUserInfo, + logoutFun, + retrievePasswordFun, + clearStorage, + registerFun, + loginSuccess, + isLogin, + currentUserInfo + }; } diff --git a/Yi.Bbs.Vue3/src/utils/request.js b/Yi.Bbs.Vue3/src/utils/request.js index 3b7c3604..a0980c5a 100644 --- a/Yi.Bbs.Vue3/src/utils/request.js +++ b/Yi.Bbs.Vue3/src/utils/request.js @@ -1,10 +1,10 @@ - import axios from 'axios'; -import { getToken } from '@/utils/auth' -export let isRelogin = { show: false }; +import {getToken} from '@/utils/auth' + +export let isRelogin = {show: false}; // import JsonBig from 'json-bigint' const myaxios = axios.create({ - baseURL:import.meta.env.VITE_APP_BASEAPI, + baseURL: import.meta.env.VITE_APP_BASEAPI, timeout: 50000, // transformResponse: [data => { // try { @@ -22,12 +22,11 @@ const myaxios = axios.create({ }) - // 请求拦截器 myaxios.interceptors.request.use(function (config) { if (getToken()) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 - } + } return config; }, function (error) { @@ -37,27 +36,25 @@ myaxios.interceptors.request.use(function (config) { // 响应拦截器 myaxios.interceptors.response.use(function (response) { //业务错误 - if(response.data.statusCode==403) - { + if (response.data.statusCode == 403) { ElMessage.error(response.data.errors) } return response; }, function (error) { const code = error.response.status; const msg = error.message; -//业务异常+应用异常,统一处理 - switch(code) - { - case 401: - ElMessage.error('登录已过期') - break; - case 403: - ElMessage.error(msg) - break; - case 500: - ElMessage.error(msg) - break; - } + //业务异常+应用异常,统一处理 + switch (code) { + case 401: + ElMessage.error('登录已过期') + break; + case 403: + ElMessage.error(msg) + break; + case 500: + ElMessage.error(msg) + break; + } return Promise.reject(error); });