feat: 增加UKEY登录,待联调

develop
时启龙 2024-08-22 16:50:27 +08:00
parent 6299a389fe
commit 1359fe4f99
14 changed files with 401 additions and 58 deletions

5
src/components.d.ts vendored
View File

@ -31,9 +31,13 @@ declare module '@vue/runtime-core' {
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTab: typeof import('element-plus/es')['ElTab']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
Empty: typeof import('./components/empty/Empty.vue')['default']
IconEpArrowDown: typeof import('~icons/ep/arrow-down')['default']
IconEpArrowLeft: typeof import('~icons/ep/arrow-left')['default']
IconEpArrowRight: typeof import('~icons/ep/arrow-right')['default']
IconEpArrowRightBold: typeof import('~icons/ep/arrow-right-bold')['default']
IconEpBell: typeof import('~icons/ep/bell')['default']
@ -43,6 +47,7 @@ declare module '@vue/runtime-core' {
IconEpLock: typeof import('~icons/ep/lock')['default']
IconEpMonitor: typeof import('~icons/ep/monitor')['default']
IconEpPosition: typeof import('~icons/ep/position')['default']
IconEpRight: typeof import('~icons/ep/right')['default']
IconEpService: typeof import('~icons/ep/service')['default']
IconEpSwitchButton: typeof import('~icons/ep/switch-button')['default']
IconEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']

View File

@ -11,6 +11,10 @@ export const enableUserStorage = false
export const userKey = 'cmcUserData'
// 本地存储的cookie kye值
export const tokenKey = 'CMC_TOKEN'
// 本地存储的天融信cookie kye值
export const trxTokenKey = 'TRX_TOKEN'
// 本地存储的 ukeypassword kye值
export const ukeyPasswordKey = 'UKEY_PASSWORD'
// 最大缓存组件实例数
export const cacheViewMax = 15
export const formSetting = {

View File

@ -123,10 +123,10 @@ export default defineComponent({
ElMessageBox.confirm('您确定要退出该系统吗?', '提示', {
type: 'warning'
}).then(async () => {
const res = await logout()
if (res.success) {
store.dispatch('permission/ResetRoutes')
}
// ResetRoutes -> ukey/Logout 退
await store.dispatch('permission/ResetRoutes')
// 退
await logout()
})
}
//

View File

@ -1,6 +1,9 @@
import { computed, onMounted, onUnmounted } from 'vue'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import { getClientHello } from 'services/ukeyAuth.js'
import { replaceToken } from 'services'
import { getToken, getUkeyPassword } from 'utils/auth'
export default function () {
const store = useStore()
@ -16,6 +19,15 @@ export default function () {
}
}
init()
// 获取最新token
async function getLastToken() {
const token = getToken()
const res = await replaceToken({ token })
if (!res.success) {
clearTimer()
store.dispatch('permission/ResetRoutes')
}
}
const operateTime = computed(() => store.state.app.operateTime)
const lockScreenTime = computed(() => store.getters.systemConfig.lockScreenTime)
@ -26,12 +38,21 @@ export default function () {
}
onMounted(() => {
timer = setInterval(() => {
getLastToken()
// 锁屏
const interval = 1000 * 60 * Number(lockScreenTime.value)
// checkUserStatus()
if (interval && new Date().getTime() - operateTime.value >= interval) {
lockScreen()
}
// 每 20s 在线检测 ueky 是否存在
if (getUkeyPassword()) {
getClientHello(getUkeyPassword()).then((checkRes: any) => {
if (checkRes.result !== 0) {
store.dispatch('permission/ResetRoutes')
}
})
}
}, 1000 * 20)
})
onUnmounted(clearTimer)

View File

@ -51,6 +51,9 @@ export function getServiceQuota(tenantId, data) {
params: wrapperParams(data)
})
}
export function replaceToken(params) {
return request.get('/sms/v1/token', { params })
}
// 获取用户权限
export function getUserPermissions() {
return request.get('/sms/v1/users/permissions')

39
src/services/trxLogin.js Normal file
View File

@ -0,0 +1,39 @@
import request from 'utils/request'
export function getLoginRandom(ngxCookie) {
return request.get(
'/sms/v1/trx/getRandomStr',
{},
{
headers: {
ngxCookie
}
}
)
}
export function trxLogin(params) {
return request.post('/sms/v1/trx/login', params)
}
export function offlineToken() {
return request.post('/sms/v1/trx/logout')
}
// 理想那边给的代码调用 getAuthToken 时需要传 ip,云管目前不传
export function getIp() {
return request({
url: '/sso/getIp',
headers: {
isToken: false
},
method: 'get'
})
}
export function queryAppList() {
return request({
url: '/queryAppList',
method: 'get'
})
}

74
src/services/ukeyAuth.js Normal file
View File

@ -0,0 +1,74 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const webssoService = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: '',
// 超时
timeout: 30000
})
// request拦截器
webssoService.interceptors.request.use(
config => {
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?'
for (const propName of Object.keys(config.params)) {
const value = config.params[propName]
var part = encodeURIComponent(propName) + '='
if (value !== null && typeof value !== 'undefined') {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
let params = propName + '[' + key + ']'
var subPart = encodeURIComponent(params) + '='
url += subPart + encodeURIComponent(value[key]) + '&'
}
} else {
url += part + encodeURIComponent(value) + '&'
}
}
}
url = url.slice(0, -1)
config.params = {}
config.url = url
}
return config
},
error => {
console.log(error)
Promise.reject(error)
}
)
// 响应拦截器
webssoService.interceptors.response.use(
res => {
console.log('返回参数::', res)
return Promise.resolve(res.data)
},
error => {
console.log('err::' + error)
ElMessage({ message: '无法获取用户信息,请确认代理程序是否启动', type: 'error' })
return Promise.reject(error)
}
)
// 获取clientHello 同步负责监测 ueky 是否拔出
export function getClientHello(password) {
return webssoService({
url: 'http://127.0.0.1:8899/client_hello',
method: 'post',
data: { userName: 'key', userPwd: password }
})
}
// 生成客户验证码
export function getClientAuth(password, serverHello, clientHello) {
return webssoService({
url: 'http://127.0.0.1:8899/client_auth',
method: 'post',
data: { userName: 'key', userPwd: password, serverHello, clientHello }
})
}

View File

@ -2,7 +2,7 @@
* Created by HaijunZhang on 2018/11/12.
*/
import { resolvePath } from 'utils/resolvePath'
import { removeToken } from 'utils/auth'
import { removeToken, removeTrxToken } from 'utils/auth'
import { getUserPermissions } from 'services'
import BlankView from '@/layouts/blank.vue'
import router, { resetRouter } from '@/router'
@ -130,20 +130,32 @@ const actions = {
},
ResetRoutes({ commit, dispatch }, redirectToLogin = true) {
return new Promise(resolve => {
resetRouter()
commit('SET_ROUTES', null)
if (enablePermissionStorage) {
localStorage.removeItem(menuKey)
const reset = () => {
resetRouter()
commit('SET_ROUTES', null)
if (enablePermissionStorage) {
localStorage.removeItem(menuKey)
}
// 重置用户信息
commit('RESET_USER', null, { root: true })
// 重置用户信息
commit('SETTING_SIDE_MENU', [], { root: true })
// 重置标签信息
dispatch('tagsView/delAllViews', null, { root: true })
removeToken()
removeTrxToken()
if (redirectToLogin) window.location.href = '/login'
resolve()
}
// 调用天融信单点退出系统
if (redirectToLogin) {
dispatch('ukey/Logout', null, { root: true }).then(res => {
if (!res.success) return
reset()
})
} else {
reset()
}
// 重置用户信息
commit('RESET_USER', null, { root: true })
// 重置用户信息
commit('SETTING_SIDE_MENU', [], { root: true })
// 重置标签信息
dispatch('tagsView/delAllViews', null, { root: true })
removeToken()
if (redirectToLogin) window.location.href = '/login'
resolve()
})
}
}

87
src/store/modules/ukey.js Normal file
View File

@ -0,0 +1,87 @@
import { getClientHello, getClientAuth } from 'services/ukeyAuth.js'
import { getLoginRandom, trxLogin, offlineToken } from 'services/trxLogin.js'
import Cookies from 'js-cookie'
import { ElMessage } from 'element-plus'
const state = {}
const mutations = {}
const actions = {
// 登录
Login({ commit }, password) {
return new Promise((resolve, reject) => {
// 调用 ukey 获取 clientHello
getClientHello(password).then(
checkRes => {
if (checkRes.result !== 0) {
Cookies.remove('ngx_cookie')
ElMessage({ message: checkRes.message, type: 'error' })
return Promise.reject(new Error(checkRes.message))
}
const clientHello = checkRes.clientHello
Cookies.set('ngx_cookie', clientHello)
// 调用天融信单点登录获取 serverHello
getLoginRandom(clientHello).then(
randomRes => {
if (!randomRes.success) {
ElMessage({ message: randomRes.message, type: 'error' })
return Promise.reject(new Error(randomRes.message))
}
let serverHello = randomRes.data
// 调用 ukey 获取 ClientAuth
getClientAuth(password, serverHello, clientHello).then(
authRes => {
// 暂未用到
const ClientAuth = authRes.clientAuth
trxLogin({
clientHello,
serverHello
}).then(
tokenRes => {
if (!tokenRes.success) {
ElMessage({ message: tokenRes.message, type: 'error' })
return Promise.reject(new Error(tokenRes.message))
}
resolve(tokenRes)
},
err => {
reject(err)
}
)
},
err => {
reject(err)
}
)
},
err => {
reject(err)
}
)
},
err => {
reject(err)
}
)
})
},
// 退出系统
Logout() {
return new Promise((resolve, reject) => {
// 调用天融信单点退出系统
offlineToken().then(
res => {
console.log('调用天融信单点退出系统', res)
resolve(res)
},
err => {
reject(err)
}
)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

View File

@ -1,7 +1,8 @@
/**
* Created by HaijunZhang on 2018/11/16.
*/
import { tokenKey } from '@/config'
import { encrypt, decrypt } from 'utils/crypto'
import { tokenKey, trxTokenKey, ukeyPasswordKey } from '@/config'
export function getToken() {
return sessionStorage.getItem(tokenKey)
@ -14,3 +15,24 @@ export function setToken(token: string) {
export function removeToken() {
return sessionStorage.removeItem(tokenKey)
}
export function getTrxToken() {
return sessionStorage.getItem(trxTokenKey)
}
export function setTrxToken(token: string) {
return sessionStorage.setItem(trxTokenKey, token)
}
export function removeTrxToken() {
localStorage.removeItem(ukeyPasswordKey)
return sessionStorage.removeItem(trxTokenKey)
}
export function getUkeyPassword() {
return decrypt(sessionStorage.getItem(ukeyPasswordKey))
}
// 用于保活与监测 ukey 是否拔出
export function setUkeyPassword(password: string) {
return localStorage.setItem(ukeyPasswordKey, encrypt(password))
}

View File

@ -55,7 +55,7 @@ const handleError = function (response) {
title = data.message
errorText = ''
}
Notification({
ElNotification({
type: 'error',
title,
message: errorText

View File

@ -16,6 +16,19 @@
</el-input>
</el-form-item>
</el-form>
<el-form :model="loginForm" ref="ukeyLoginFormRef" @keyup.enter.prevent="handleUkeyLogin" @submit.prevent>
<el-form-item prop="ukeyPassword" :rules="[{ ...required, message: '请输入 UKEY 密码' }]" class="form-item-wrapper">
<el-input type="password" v-model="loginForm.ukeyPassword" placeholder="请输入 UKEY 密码" show-password>
<template #append>
<el-button class="text-white login-btn" @click="handleUkeyLogin()" :loading="loading">
<template #icon>
<el-icon><IconEpRight /></el-icon>
</template>
</el-button>
</template>
</el-input>
</el-form-item>
</el-form>
<el-button class="switch-button" link @click="switchUser()">
<template #icon>
<IconEpArrowLeft />
@ -39,12 +52,13 @@ import { login } from 'services'
import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, toRefs } from 'vue'
import setLoginData from './tools'
import { required } from '@/validate'
import { setUkeyPassword } from 'utils/auth'
export default defineComponent({
setup() {
const state = reactive({
loginForm: {
password: ''
password: '',
ukeyPassword: ''
},
loading: false
})
@ -93,6 +107,7 @@ export default defineComponent({
store.dispatch('permission/ResetRoutes')
}
const loginFormRef = ref()
const ukeyLoginFormRef = ref()
const router = useRouter()
async function handleLogin() {
const { validate } = loginFormRef.value as any
@ -103,7 +118,7 @@ export default defineComponent({
account: userData.value.account,
password: encrypt(state.loginForm.password),
isManager: true
})
}).finally(() => (state.loading = false))
if (res.success) {
setLoginData(res.data)
router.replace(lockData.path)
@ -116,7 +131,31 @@ export default defineComponent({
})
)
}
state.loading = false
})
}
async function handleUkeyLogin() {
const { validate } = ukeyLoginFormRef.value as any
validate(async (valid: boolean) => {
if (!valid) return
state.loading = true
const res = await store
.dispatch('ukey/Login', state.loginForm.ukeyPassword)
.catch(res => console.log(res))
.finally(() => (state.loading = false))
if (res.success) {
// ukey
setUkeyPassword(state.loginForm.ukeyPassword)
setLoginData(res.data)
router.replace(lockData.path)
store.commit('SET_OPERATETIME')
localStorage.setItem(
'lockData',
JSON.stringify({
...lockData,
isLock: false
})
)
}
})
}
return {
@ -124,9 +163,11 @@ export default defineComponent({
currentTime,
userData,
loginFormRef,
ukeyLoginFormRef,
required,
switchUser,
handleLogin
handleLogin,
handleUkeyLogin
}
}
})

View File

@ -13,36 +13,52 @@
<span class="desc-title">{{ configs.promotionalTitle }}</span>
<span class="desc-remark">{{ configs.promotionalContent }}</span>
</div>
<el-form :model="loginForm" ref="loginFormRef" label-position="left" label-width="0px" class="card-box login-form" @keyup.enter="handleLogin">
<div class="login-title">账号登录</div>
<el-form-item class="login-form-item" prop="account" :rules="[{ ...required, message: '请输入用户名' }]">
<el-input v-model="loginForm.account" autocomplete="off" placeholder="登录账户">
<template #prefix>
<el-icon>
<icon-ep-user />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-tooltip :visible="capsTooltip" content="大写锁定已打开" placement="right" :manual="true">
<el-form-item class="login-form-item" prop="password" :rules="[{ ...required, message: '请输入密码' }]">
<el-input show-password v-model="loginForm.password" placeholder="密码" @blur="capsTooltip = false" @keyup="checkCapslock">
<template #prefix>
<el-icon><icon-ep-lock /></el-icon>
</template>
</el-input>
</el-form-item>
</el-tooltip>
<el-form-item class="login-form-item">
<div class="operate-region">
<span class="remember">
<el-switch v-model="remember"></el-switch>
<span class="m-l-xs">记住密码</span>
</span>
<a :href="`mailto:${configs.helpInformationLink}`" type="text" class="text-info help-info" :title="configs.helpInformationContent">{{ configs.helpInformationContent }}</a>
</div>
</el-form-item>
<el-button class="login-btn" type="primary" :loading="loading" @click="handleLogin"></el-button>
<el-form :model="loginForm" ref="loginFormRef" label-position="left" label-width="0px" class="card-box login-form" @keyup.enter="activeName === 'normal' ? handleLogin : handleUkeyLogin">
<el-tabs v-model="activeName">
<el-tab-pane label="云管登录" name="normal">
<div class="login-title">账号登录</div>
<el-form-item class="login-form-item" prop="account" :rules="[{ ...required, message: '请输入用户名' }]">
<el-input v-model="loginForm.account" autocomplete="off" placeholder="登录账户">
<template #prefix>
<el-icon>
<icon-ep-user />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-tooltip :visible="capsTooltip" content="大写锁定已打开" placement="right" :manual="true">
<el-form-item class="login-form-item" prop="password" :rules="[{ ...required, message: '请输入密码' }]">
<el-input show-password v-model="loginForm.password" placeholder="密码" @blur="capsTooltip = false" @keyup="checkCapslock">
<template #prefix>
<el-icon><icon-ep-lock /></el-icon>
</template>
</el-input>
</el-form-item>
</el-tooltip>
<el-form-item class="login-form-item">
<div class="operate-region">
<span class="remember">
<el-switch v-model="remember"></el-switch>
<span class="m-l-xs">记住密码</span>
</span>
<a :href="`mailto:${configs.helpInformationLink}`" type="text" class="text-info help-info" :title="configs.helpInformationContent">{{ configs.helpInformationContent }}</a>
</div>
</el-form-item>
<el-button class="login-btn" type="primary" :loading="loading" @click="handleLogin"></el-button>
</el-tab-pane>
<el-tab-pane label="UKEY登录待联调" name="UKEY">
<el-tooltip :visible="capsTooltip" content="大写锁定已打开" placement="right" :manual="true">
<el-form-item class="login-form-item" prop="password" :rules="[{ ...required, message: '请输入UKEY密码' }]">
<el-input show-password v-model="loginForm.ukeyPassword" placeholder="UKEY密码" @blur="capsTooltip = false" @keyup="checkCapslock">
<template #prefix>
<el-icon><icon-ep-lock /></el-icon>
</template>
</el-input>
</el-form-item>
</el-tooltip>
<el-button class="login-btn" type="primary" :loading="loading" @click="handleUkeyLogin"></el-button>
</el-tab-pane>
</el-tabs>
</el-form>
</div>
<div class="copyright-info">{{ configs.copyrightInformation }}</div>
@ -56,11 +72,14 @@ import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import setLoginData from './tools'
import { required } from '@/validate'
import { setUkeyPassword } from 'utils/auth'
import type { FormInstance } from 'element-plus'
const activeName = ref('normal')
const loginForm = reactive({
account: '',
password: ''
password: '',
ukeyPassword: ''
})
const remember = ref(false)
const loading = ref(false)
@ -116,7 +135,22 @@ function handleLogin() {
}
})
}
// ukey
async function handleUkeyLogin() {
if (!loginFormRef.value) return
loginFormRef.value.validate(async valid => {
if (valid) {
loading.value = true
const res = await store.dispatch('ukey/Login', loginForm.ukeyPassword).catch(res => console.log(res))
if (res.success) {
// ukey
setUkeyPassword(loginForm.ukeyPassword)
goLogin(res.data)
}
loading.value = false
}
})
}
function checkCapslock({ shiftKey, key }: { shiftKey: boolean; key: string }) {
if (key && key.length === 1) {
if ((shiftKey && key >= 'a' && key <= 'z') || (!shiftKey && key >= 'A' && key <= 'Z')) {

View File

@ -1,4 +1,4 @@
import { setToken } from 'utils/auth'
import { setToken, setTrxToken } from 'utils/auth'
import { enablePermissionStorage, menuKey, enableUserStorage, userKey } from '@/config'
export default function setLoginData(data: any) {
@ -12,4 +12,5 @@ export default function setLoginData(data: any) {
localStorage.setItem('buttonData', JSON.stringify(data.buttons || []))
localStorage.removeItem('lockData')
setToken(data.token)
setTrxToken(data.trxToken)
}