单点登录
This commit is contained in:
parent
f4a21daa0e
commit
f000d0aa3f
@ -113,7 +113,7 @@ export default {
|
||||
small: '小'
|
||||
},
|
||||
login: {
|
||||
welcome: '欢迎使用本系统',
|
||||
welcome: '登录中心',
|
||||
message: '开箱即用的中后台管理系统',
|
||||
tenantname: '租户名称',
|
||||
username: '用户名',
|
||||
|
@ -186,7 +186,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: '/sso',
|
||||
component: () => import('@/views/Login/Login.vue'),
|
||||
component: () => import('@/views/Login/components/SSOLoginCustom.vue'),
|
||||
name: 'SSOLogin',
|
||||
meta: {
|
||||
hidden: true,
|
||||
|
@ -20,12 +20,14 @@ export const getRefreshToken = () => {
|
||||
|
||||
// 设置token
|
||||
export const setToken = (token: TokenType) => {
|
||||
document.cookie = "LUNDU_LOGIN=true; max-age=1800; path=/"; // 设置cookie登录状态
|
||||
wsCache.set(RefreshTokenKey, token.refreshToken)
|
||||
wsCache.set(AccessTokenKey, token.accessToken)
|
||||
}
|
||||
|
||||
// 删除token
|
||||
export const removeToken = () => {
|
||||
document.cookie = "LUNDU_LOGIN=false; max-age=0; path=/"; // 清除cookie登录状态
|
||||
wsCache.delete(AccessTokenKey)
|
||||
wsCache.delete(RefreshTokenKey)
|
||||
}
|
||||
|
@ -55,7 +55,9 @@
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-4" style="margin: 20px 0;">
|
||||
<el-card style="width: 420px;text-align: center;cursor: pointer;" shadow="hover" @click="goToXXLJob">任务调度中心</el-card>
|
||||
</div>
|
||||
<!-- <el-row class="mt-8px" :gutter="8" justify="space-between">
|
||||
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
|
||||
<el-card shadow="never">
|
||||
@ -387,5 +389,8 @@ const getAllApi = async () => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const goToXXLJob = async () => {
|
||||
window.location.href = 'http://127.0.0.1:9090/xxl-job-admin/'
|
||||
}
|
||||
getAllApi()
|
||||
</script>
|
||||
|
200
src/views/Login/components/SSOLoginCustom.vue
Normal file
200
src/views/Login/components/SSOLoginCustom.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div v-show="false" class="form-cont">
|
||||
<!-- 应用名 -->
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
<el-tabs class="form" style="float: none" value="uname">
|
||||
<el-tab-pane :label="client.name" name="uname" />
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-form :model="formData" class="login-form">
|
||||
<!-- 授权范围的选择 -->
|
||||
此第三方应用请求获得以下权限:
|
||||
<el-form-item prop="scopes">
|
||||
<el-checkbox-group v-model="formData.scopes">
|
||||
<el-checkbox
|
||||
v-for="scope in queryParams.scopes"
|
||||
:key="scope"
|
||||
:label="scope"
|
||||
style="display: block; margin-bottom: -10px"
|
||||
>
|
||||
{{ formatScope(scope) }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<!-- 下方的登录按钮 -->
|
||||
<el-form-item class="w-1/1">
|
||||
<el-button
|
||||
:loading="formLoading"
|
||||
class="w-6/10"
|
||||
type="primary"
|
||||
@click.prevent="handleAuthorize(true)"
|
||||
>
|
||||
<span v-if="!formLoading">同意授权</span>
|
||||
<span v-else>授 权 中...</span>
|
||||
</el-button>
|
||||
<el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import * as OAuth2Api from '@/api/login/oauth2'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
defineOptions({ name: 'SSOLogin' })
|
||||
|
||||
const route = useRoute() // 路由
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const { getLoginState, setLoginState } = useLoginState()
|
||||
|
||||
const client = ref({
|
||||
// 客户端信息
|
||||
name: '',
|
||||
logo: ''
|
||||
})
|
||||
interface queryType {
|
||||
responseType: string
|
||||
clientId: string
|
||||
redirectUri: string
|
||||
state: string
|
||||
scopes: string[]
|
||||
}
|
||||
const queryParams = reactive<queryType>({
|
||||
// URL 上的 client_id、scope 等参数
|
||||
responseType: '',
|
||||
clientId: '',
|
||||
redirectUri: '',
|
||||
state: '',
|
||||
scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取
|
||||
})
|
||||
const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单
|
||||
interface formType {
|
||||
scopes: string[]
|
||||
}
|
||||
const formData = reactive<formType>({
|
||||
scopes: [] // 已选中的 scope 数组
|
||||
})
|
||||
const formLoading = ref(false) // 表单是否提交中
|
||||
|
||||
/** 初始化授权信息 */
|
||||
const init = async () => {
|
||||
// 防止在没有登录的情况下循环弹窗
|
||||
if (typeof route.query.client_id === 'undefined') return
|
||||
// 解析参数
|
||||
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
|
||||
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
|
||||
queryParams.responseType = route.query.response_type as string
|
||||
queryParams.clientId = route.query.client_id as string
|
||||
queryParams.redirectUri = route.query.redirect_uri as string
|
||||
queryParams.state = route.query.state as string
|
||||
if (route.query.scope) {
|
||||
queryParams.scopes = (route.query.scope as string).split(' ')
|
||||
}
|
||||
|
||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
|
||||
if (queryParams.scopes.length > 0) {
|
||||
const data = await doAuthorize(true, queryParams.scopes, [])
|
||||
if (data) {
|
||||
location.href = data
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取授权页的基本信息
|
||||
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
|
||||
client.value = data.client
|
||||
// 解析 scope
|
||||
let scopes
|
||||
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes
|
||||
if (queryParams.scopes.length > 0) {
|
||||
scopes = []
|
||||
for (const scope of data.scopes) {
|
||||
if (queryParams.scopes.indexOf(scope.key) >= 0) {
|
||||
scopes.push(scope)
|
||||
}
|
||||
}
|
||||
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
|
||||
} else {
|
||||
scopes = data.scopes
|
||||
for (const scope of scopes) {
|
||||
queryParams.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
// 生成已选中的 checkedScopes
|
||||
for (const scope of scopes) {
|
||||
if (scope.value) {
|
||||
formData.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理授权的提交 */
|
||||
const handleAuthorize = async (approved) => {
|
||||
// 计算 checkedScopes + uncheckedScopes
|
||||
let checkedScopes
|
||||
let uncheckedScopes
|
||||
if (approved) {
|
||||
// 同意授权,强制选择全部权限
|
||||
checkedScopes = queryParams.scopes
|
||||
uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
|
||||
} else {
|
||||
// 拒绝,则都是取消
|
||||
checkedScopes = []
|
||||
uncheckedScopes = queryParams.scopes
|
||||
}
|
||||
// 提交授权的请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await doAuthorize(false, checkedScopes, uncheckedScopes)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
location.href = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 调用授权 API 接口 */
|
||||
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
||||
return OAuth2Api.authorize(
|
||||
queryParams.responseType,
|
||||
queryParams.clientId,
|
||||
queryParams.redirectUri,
|
||||
queryParams.state,
|
||||
autoApprove,
|
||||
checkedScopes,
|
||||
uncheckedScopes
|
||||
)
|
||||
}
|
||||
|
||||
/** 格式化 scope 文本 */
|
||||
const formatScope = (scope) => {
|
||||
// 格式化 scope 授权范围,方便用户理解。
|
||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
|
||||
switch (scope) {
|
||||
case 'user.read':
|
||||
return '访问你的个人信息'
|
||||
case 'user.write':
|
||||
return '修改你的个人信息'
|
||||
default:
|
||||
return scope
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
async (route: RouteLocationNormalizedLoaded) => {
|
||||
if (route.name === 'SSOLogin') {
|
||||
setLoginState(LoginStateEnum.SSO)
|
||||
await init()
|
||||
handleAuthorize(true)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user