import Vue from "vue"; import { apiService } from "./api.service"; import oauthApi from "../constants/api/oauth.api"; import { RestfulResponse } from "../model/RestfulResponse"; import { OauthConfig } from "../model/OauthConfig"; import { tokenStore } from "../stores/token.store"; import { Token } from "../model/Token"; import { Code } from "../constants/enum/code.enum"; import { RestApi } from "../model/RestApi"; import { SyncCacheRequest } from "../model/SyncCacheRequest"; import { IdentityType } from "../constants/enum/identity-type.enum"; import { routeTopologyStore } from "../stores/route-topology.store"; import { userStore } from "../stores/user.store"; import sysUserInfoApi from "../constants/api/sys-user-info.api"; import { CasConfig } from "../model/CasConfig"; import { CoreConfig } from "../model/CoreConfig"; import { SysUserInfo } from "../model/SysUserInfo"; import { urlUtilService } from "../services/url-util.service"; import { redirectUriStore } from "../stores/redirect-uri.store"; import { StatusCode } from "../constants/enum/status-code.enum"; import { LoginRequest } from "../model/LoginRequest"; import { LoginResponse } from "../model/LoginResponse"; import { eventService } from "../services/event.service"; import { AuthEventType } from "../constants/enum/auth-event-type.enum"; import { AuthType } from "../constants/enum/auth-type.enum"; import { WxQyOauth } from "../model/WxQyOauth"; /** * @description: 授权服务 * @author ChenRui * @date 2020/8/27 10:48 */ class OauthService { isLogin = false; // 是否登录 static readonly BASE_TOKEN_TYPE: string = "Basic "; static readonly AUTH_EVENT_NAME: string = "auth"; /** * @description: 全局配置-core * @author ChenRui * @date 2021/8/6 15:58 */ get coreConfig(): CoreConfig { if (Vue.prototype.$dc != null) { return Vue.prototype.$dc; } return new CoreConfig(); } /** * @description: 全局配置-auth * @author ChenRui * @date 2021/8/6 15:59 */ get authConfig(): OauthConfig { if (this.coreConfig != null && this.coreConfig.oauth != null) { return this.coreConfig.oauth; } return new OauthConfig(); } /** * @description: 全局配置-企业微信auth * @author ChenRui * @date 2021/8/6 15:59 */ get wxQyOauth(): WxQyOauth { if (this.coreConfig != null && this.coreConfig.wxQyOauth != null) { return this.coreConfig.wxQyOauth; } return new WxQyOauth(); } /** * @description: 全局配置-cas * @author ChenRui * @date 2021/8/6 15:59 */ get casConfig(): CasConfig { if (this.coreConfig != null && this.coreConfig.oauth != null) { return this.coreConfig.cas; } return new CasConfig(); } /** * @description: 检查登录状态并进行登录 * @author ChenRui * @date 2021/8/4 10:52 */ checkAndLogin(): Promise { if (this.coreConfig.authPreferTo === AuthType.OAUTH.code) { if (this.authConfig.enableNoticeMode) { if (!this.isLoadedToken) { // 通知宿主进行登录 setTimeout(() => { eventService.emit(OauthService.AUTH_EVENT_NAME, { type: AuthEventType.LOGIN.code }); }); return Promise.resolve(RestfulResponse.failure("正在前往登录")); } else if (!this.isTokenExpiration) { // 判断用户令牌是否有效 // 若用户已经登录但令牌过期,此时需要使用refresh_token进行令牌刷新 return this.refreshToken(); } } else { if (!this.isLoadedToken) { // 判断用户是否登录 const code = urlUtilService.getQueryValue("code"); const state = urlUtilService.getQueryValue("state"); if (code && state === this.authConfig.state) { // 在授权中心登录成功后重定向回用户页面,检查url是否包含授权码与自定义状态码,如果包含则使用授权码向授权服务器请求用户令牌 return this.getTokenByAuthCode(); } else if (this.wxQyOauth.enable && code && state === this.wxQyOauth.state) { // 由企业微信授权后重定向回用户界面,检查url是否包含授权码与自定义状态码,如果包含则使用授权码向后端指定接口兑换用户信息 return this.getTokenByWxQyAuthCode(); } else { // 本地尚未存储令牌,同时当前url也未包含授权码,则导航到授权中心进行登录 return this.login(); } } else if (!this.isTokenExpiration) { // 判断用户令牌是否有效 // 若用户已经登录但令牌过期,此时需要使用refresh_token进行令牌刷新 return this.refreshToken(); } } return Promise.resolve(RestfulResponse.success(null, "用户已登录")); } else if (this.coreConfig.authPreferTo === AuthType.CAS.code) { // 判断用户是否登录 if (!this.isLoadedToken) { const voucher = urlUtilService.getQueryValue(this.casConfig.voucherKey); if (voucher) { // 检查url是否包含三方cas系统认证后提供的voucher票据,如果包含则使用voucher票据想指定服务器请求用户信息 return this.getTokenByAuthCasVoucher(voucher); } else { // 本地尚未存储令牌,同时当前url也未包含授权码,则导航到授权中心进行登录 return this.caslogin(); } } else if (!this.isTokenExpiration) { // 判断用户令牌是否有效 // 若用户已经登录但令牌过期,此时需要使用refresh_token进行令牌刷新 return this.refreshToken(); } return Promise.resolve(RestfulResponse.success(null, "用户已登录")); } else { return Promise.resolve(RestfulResponse.failure("尚不支持当前的认证类型")); } } /** * @description: 通过授权码获取令牌 * @author ChenRui * @date 2021/8/4 11:53 */ getTokenByAuthCode(): Promise { const query: any = { grant_type: "authorization_code", code: urlUtilService.getQueryValue("code"), redirect_uri: this.genRedirectUri(), }; const basicAuth: any = this.genBasicAuth(); // 基础认证消息头 const restApi: RestApi = new RestApi(oauthApi.token); restApi.url = this.authConfig.authorizeUrl + oauthApi.token.url; return apiService.general(restApi, query, undefined, basicAuth).then((res: any) => { if (res && res.access_token && res.jti) { tokenStore.token = res; // 缓存令牌信息 return Promise.resolve(this.syncUserCache(res)); } else { tokenStore.token = undefined; // 清空缓存的令牌信息 userStore.user = undefined; // 清空用户信息 return RestfulResponse.failure(res && res.msg ? res.msg : "令牌加载失败"); } }); } /** * @description: * @author ChenRui * @date 2022/2/26 20:46 */ getTokenByAuthCasVoucher(voucher: string): Promise { const param: any = { [this.casConfig.voucherKey]: voucher, }; const basicAuth: any = this.genBasicAuth(); // 基础认证消息头 const restApi: RestApi = new RestApi(oauthApi.token); restApi.url = this.coreConfig.baseApi + this.casConfig.innerLoginUri; return apiService.general(restApi, undefined, param, basicAuth).then((res: any) => { if (res.code === Code.SUCCESS.code) { tokenStore.token = new Token(res.data?.token); // 缓存令牌信息 userStore.user = res.data?.sysUserInfo; // 缓存用户信息 return res; } else { tokenStore.token = undefined; // 清空缓存的令牌信息 userStore.user = undefined; // 清空用户信息 return RestfulResponse.failure(res && res.msg ? res.msg : "CAS登录失败"); } }); } /** * @description: 通过企业微信获取授权码 * @author ChenRui * @date 2022/2/26 20:46 */ getTokenByWxQyAuthCode(): Promise { const query: any = { code: urlUtilService.getQueryValue("code"), }; const restApi: RestApi = new RestApi(sysUserInfoApi.qyWechatOauthLogin); restApi.url = this.coreConfig.baseApi + sysUserInfoApi.qyWechatOauthLogin.url; return apiService.general(restApi, query).then((res: any) => { if (res.code === Code.SUCCESS.code) { tokenStore.token = new Token(res.data?.token); // 缓存令牌信息 userStore.user = res.data?.sysUserInfo; // 缓存用户信息 return res; } else { tokenStore.token = undefined; // 清空缓存的令牌信息 userStore.user = undefined; // 清空用户信息 return RestfulResponse.failure(res && res.msg ? res.msg : "企业微信登录失败"); } }); } /** * @description: 单点登录 * @author ChenRui * @date 2021/8/4 10:49 */ login(): Promise { const authorizeUrl = this.authConfig.authorizeUrl + oauthApi.authorize.url; const param = { response_type: "code", client_id: this.authConfig.clientId, scope: this.authConfig.scope, state: this.authConfig.state, redirect_uri: this.genRedirectUri(), }; window.location.href = apiService.urlQueryConvert(authorizeUrl, param); return Promise.resolve(RestfulResponse.failure("用户未登录,正在前往登录入口")); } /** * @description: 获取图形验证码 * @author LiPengFei * @date 2023/8/7 17:24 */ getKaptcha(): Promise { const restApi: RestApi = new RestApi(sysUserInfoApi.getKaptcha); const url = sysUserInfoApi.getKaptcha.url; restApi.url = this.coreConfig.baseApi + url; return apiService.general(restApi, undefined, {}).then((response: RestfulResponse) => { return response; }); } /** * @description: 用户登录-密码模式 * @author ChenRui * @date 2022/1/25 17:24 */ simpleLogin(loginRequest: LoginRequest): Promise { const restApi: RestApi = new RestApi(sysUserInfoApi.login); const url = this.authConfig.simpleLoginUri ? this.authConfig.simpleLoginUri : sysUserInfoApi.login.url; restApi.url = this.coreConfig.baseApi + url; return apiService.general(restApi, undefined, loginRequest).then((response: RestfulResponse) => { if (response.code === Code.SUCCESS.code) { userStore.user = response.data?.sysUserInfo; // 缓存用户信息 tokenStore.token = new Token(response.data?.token); // 缓存令牌信息 } else { userStore.user = undefined; // 缓存用户信息 tokenStore.token = undefined; // 缓存令牌信息 } return response; }); } /** * @description: 扩展登录模式-cas登录 * @author ChenRui * @date 2022/2/26 20:44 */ caslogin(): Promise { const authorizeUrl = this.casConfig.authorizeUrl; const param = { [this.casConfig.redirectUriKey]: this.genRedirectUri(), }; window.location.href = apiService.urlQueryConvert(authorizeUrl, param); return Promise.resolve(RestfulResponse.failure("用户未登录,正在前往登录入口")); } /** * @description: 刷新令牌 * @author ChenRui * @date 2021/8/4 17:57 */ refreshToken(): Promise { if (this.authConfig.enableNoticeMode) { const params = { userId: userStore.user?.userId, tenantId: userStore.user?.tenantId, refreshToken: tokenStore.token?.refresh_token, userIdentity: tokenStore.token?.jti, }; const restApi: RestApi = new RestApi(sysUserInfoApi.refreshToken); restApi.url = this.coreConfig.baseApi + sysUserInfoApi.refreshToken.url; return apiService.general(restApi, undefined, params).then((response: RestfulResponse) => { if (response && response.code === Code.SUCCESS.code) { tokenStore.token = new Token(response.data); // 缓存令牌信息 return this.syncUserCache(response.data); } else { tokenStore.token = undefined; // 清空缓存的令牌信息 userStore.user = undefined; // 清空用户信息 return new RestfulResponse({ code: Code.FAIL.code, msg: "令牌刷新失败", meta: { statusCode: StatusCode.REFRESH_TOKEN_FAILED.code, }, }); } }); } else { const basicAuth: any = this.genBasicAuth(); // 基础认证消息头 const restApi: RestApi = new RestApi(oauthApi.token); restApi.url = this.authConfig.authorizeUrl + oauthApi.token.url; const query: any = { grant_type: "refresh_token", refresh_token: tokenStore.token?.refresh_token, }; return apiService.general(restApi, query, undefined, basicAuth).then((token: Token) => { if (token && token.access_token && token.jti) { tokenStore.token = token; // 缓存令牌信息 return this.syncUserCache(token); } else { tokenStore.token = undefined; // 清空缓存的令牌信息 userStore.user = undefined; // 清空用户信息 return new RestfulResponse({ code: Code.FAIL.code, msg: "令牌刷新失败", meta: { statusCode: StatusCode.REFRESH_TOKEN_FAILED.code, }, }); } }); } } /** * @description: 登出 * @author ChenRui * @date 2020/12/18 17:53 */ logOut(authorizeUrl?: string): Promise { // 清空服务器端的用户数据缓存 if (userStore.user && userStore.user.userId) { const query = { userId: userStore.user.userId, userIdentity: tokenStore.token?.jti, }; const restApi: RestApi = new RestApi(sysUserInfoApi.logout); restApi.url = this.coreConfig.baseApi + sysUserInfoApi.logout.url; return apiService .general(restApi, query) .then((response: RestfulResponse) => { return this.cleanOauthUserInfo(); }) .catch(() => { return this.cleanOauthUserInfo(); }); } else { return this.cleanOauthUserInfo(); } } /** * @description: 生成重定向地址 * @author ChenRui * @date 2021/8/6 17:36 */ genRedirectUri(isUseCache = true): string { if (this.authConfig.redirectUri) { return this.authConfig.redirectUri; } else { if (isUseCache && !!redirectUriStore.redirectUri) { return redirectUriStore.redirectUri; } else { const code = urlUtilService.getQueryValue("code"); const state = urlUtilService.getQueryValue("state"); if (code && state === this.authConfig.state) { const url = urlUtilService.deleteParameter(window.location.href, "code", "state"); redirectUriStore.redirectUri = url; } else { redirectUriStore.redirectUri = window.location.href; } } return redirectUriStore.redirectUri; } } /** * @description: 清空授权中心缓存数据 * @author ChenRui * @date 2021/8/6 18:20 */ private cleanOauthUserInfo(): Promise { routeTopologyStore.inited = false; // 清空路由拓扑 const userStoreBack = { ...userStore.user }; userStore.user = undefined; tokenStore.token = undefined; if (this.authConfig.enableNoticeMode) { return Promise.resolve(RestfulResponse.success(userStoreBack, "登出成功")); } else { const user = localStorage.getItem("core:user"); const { tenantId = "" } = JSON.parse(user || "{}"); const restApi: RestApi = new RestApi(oauthApi.ssoLogout); restApi.url = this.authConfig.authorizeUrl + oauthApi.ssoLogout.url; setTimeout(() => { window.location.href = restApi.url + "?redirect_uri=" + this.genRedirectUri(false) + "&tenantId=" + tenantId; redirectUriStore.redirectUri = undefined; }, 100); return Promise.resolve(RestfulResponse.success(userStoreBack, "登出成功")); } } /** * @description: 同步用户信息 * @author ChenRui * @date 2021/8/4 21:23 */ private syncUserCache(token: Token): Promise { const params: SyncCacheRequest = { userName: token.userName, tenantId: userStore.user?.tenantId, userIdentity: token.jti, identityType: this.authConfig.identityType || IdentityType.password.code, }; const restApi: RestApi = new RestApi(sysUserInfoApi.syncUserCache); restApi.url = this.coreConfig.baseApi + sysUserInfoApi.syncUserCache.url; return apiService.general(restApi, undefined, params).then((response: RestfulResponse) => { if (response.code === Code.SUCCESS.code) { userStore.user = response.data; // 缓存用户信息 } return response; }); } /** * @description: 创建基础认证消息头 * @author ChenRui * @date 2021/8/4 21:18 */ private genBasicAuth(): any { const clientCredentials: string = this.authConfig.clientId + ":" + this.authConfig.clientSecret; const authorization = OauthService.BASE_TOKEN_TYPE + window.btoa(clientCredentials); const basicAuth: any = { headers: { Authorization: authorization, }, }; return basicAuth; } /** * @description: 检查是否获加载完令牌 * @author ChenRui * @date 2020/8/27 11:08 */ get isLoadedToken(): boolean { const token: Token | undefined = tokenStore.token; return token != null && !!token.access_token; } /** * @description: 检查是否获加载完令牌 * @author ChenRui * @date 2021/8/6 23:36 */ get isLoadedUserInfo(): boolean { const sysUserInfo: SysUserInfo | undefined = userStore.user; return sysUserInfo != null && !!sysUserInfo.userId; } /** * @description: 检查令牌是否有效 * @author ChenRui * @date 2021/8/4 11:13 */ get isTokenExpiration(): boolean { const token: Token | undefined = tokenStore.token; if (token != null && token.expires_in) { const currentTimeStamp = new Date().getTime(); const expiratTimeStamp = Number(token.time) + Number((token.expires_in - 300) * 1000); return !(currentTimeStamp > expiratTimeStamp); } return false; } } const oauthService = new OauthService(); export { OauthService, oauthService };