REVIEW 单点登录界面

This commit is contained in:
YunaiV 2023-04-09 00:21:27 +08:00
parent 0f0ba8b8a9
commit 276e82c5a8
6 changed files with 162 additions and 196 deletions

View File

@ -1,13 +1,6 @@
import request from '@/config/axios' import request from '@/config/axios'
import { getRefreshToken } from '@/utils/auth' import { getRefreshToken } from '@/utils/auth'
import type { UserLoginVO } from './types' import type { UserLoginVO } from './types'
import { service } from '@/config/axios/service'
export interface CodeImgResult {
captchaOnOff: boolean
img: string
uuid: string
}
export interface SmsCodeVO { export interface SmsCodeVO {
mobile: string mobile: string
@ -74,51 +67,3 @@ export const getCode = (data) => {
export const reqCheck = (data) => { export const reqCheck = (data) => {
return request.postOriginal({ url: 'system/captcha/check', data }) return request.postOriginal({ url: 'system/captcha/check', data })
} }
// ========== OAUTH 2.0 相关 ==========
export type scopesType = string[]
export interface paramsType {
responseType: string
clientId: string
redirectUri: string
state: string
scopes: scopesType
}
export const getAuthorize = (clientId) => {
return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
}
export function authorize(
responseType: string,
clientId: string,
redirectUri: string,
state: string,
autoApprove: boolean,
checkedScopes: scopesType,
uncheckedScopes: scopesType
) {
// 构建 scopes
const scopes = {}
for (const scope of checkedScopes) {
scopes[scope] = true
}
for (const scope of uncheckedScopes) {
scopes[scope] = false
}
// 发起请求
return service({
url: '/system/oauth2/authorize',
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
params: {
response_type: responseType,
client_id: clientId,
redirect_uri: redirectUri,
state: state,
auto_approve: autoApprove,
scope: JSON.stringify(scopes)
},
method: 'post'
})
}

View File

@ -0,0 +1,41 @@
import request from '@/config/axios'
// 获得授权信息
export const getAuthorize = (clientId: string) => {
return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
}
// 发起授权
export const authorize = (
responseType: string,
clientId: string,
redirectUri: string,
state: string,
autoApprove: boolean,
checkedScopes: string[],
uncheckedScopes: string[]
) => {
// 构建 scopes
const scopes = {}
for (const scope of checkedScopes) {
scopes[scope] = true
}
for (const scope of uncheckedScopes) {
scopes[scope] = false
}
// 发起请求
return request.post({
url: '/system/oauth2/authorize',
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
params: {
response_type: responseType,
client_id: clientId,
redirect_uri: redirectUri,
state: state,
auto_approve: autoApprove,
scope: JSON.stringify(scopes)
}
})
}

View File

@ -26,17 +26,3 @@ export type UserVO = {
loginIp: string loginIp: string
loginDate: string loginDate: string
} }
export type UserInfoVO = {
permissions: []
roles: []
user: {
avatar: string
id: number
nickname: string
}
}
export type TentantNameVO = {
name: string
}

View File

@ -129,12 +129,6 @@ export default {
btnMobile: '手机登录', btnMobile: '手机登录',
btnQRCode: '二维码登录', btnQRCode: '二维码登录',
qrcode: '扫描二维码登录', qrcode: '扫描二维码登录',
sso: {
user: {
read: '访问你的个人信息',
write: '修改你的个人信息'
}
},
btnRegister: '注册', btnRegister: '注册',
SmsSendMsg: '验证码已发送' SmsSendMsg: '验证码已发送'
}, },

View File

@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
Descriptions: typeof import('./../components/Descriptions/src/Descriptions.vue')['default'] Descriptions: typeof import('./../components/Descriptions/src/Descriptions.vue')['default']
Dialog: typeof import('./../components/Dialog/src/Dialog.vue')['default'] Dialog: typeof import('./../components/Dialog/src/Dialog.vue')['default']
DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
DocAlert: typeof import('./../components/DocAlert/index.vue')['default']
Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
ElBadge: typeof import('element-plus/es')['ElBadge'] ElBadge: typeof import('element-plus/es')['ElBadge']

View File

@ -1,178 +1,178 @@
<template> <template>
<!-- 表单 --> <div v-show="ssoVisible" class="form-cont">
<div v-show="getShow" class="form-cont"> <!-- 应用名 -->
<!-- <LoginFormTitle style="width: 100%" />--> <LoginFormTitle style="width: 100%" />
<el-tabs class="form" style="float: none" value="uname"> <el-tabs class="form" style="float: none" value="uname">
<el-tab-pane :label="'三方授权(' + client.name + ')'" name="uname" /> <el-tab-pane :label="client.name" name="uname" />
</el-tabs> </el-tabs>
<div> <div>
<el-form ref="ssoForm" :model="loginForm" class="login-form"> <el-form :model="formData" class="login-form">
<!-- 授权范围的选择 --> <!-- 授权范围的选择 -->
此第三方应用请求获得以下权限 此第三方应用请求获得以下权限
<el-form-item prop="scopes"> <el-form-item prop="scopes">
<el-checkbox-group v-model="loginForm.scopes"> <el-checkbox-group v-model="formData.scopes">
<el-checkbox <el-checkbox
v-for="scope in params.scopes" v-for="scope in queryParams.scopes"
:key="scope" :key="scope"
:label="scope" :label="scope"
style="display: block; margin-bottom: -10px" style="display: block; margin-bottom: -10px"
>{{ formatScope(scope) }} >
{{ formatScope(scope) }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<!-- 下方的登录按钮 --> <!-- 下方的登录按钮 -->
<el-form-item style="width: 100%"> <el-form-item class="w-1/1">
<el-button <el-button
:loading="loading" :loading="formLoading"
size="small" class="w-6/10"
style="width: 60%"
type="primary" type="primary"
@click.prevent="handleAuthorize(true)" @click.prevent="handleAuthorize(true)"
> >
<span v-if="!loading">同意授权</span> <span v-if="!formLoading">同意授权</span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
<el-button size="small" style="width: 36%" @click.prevent="handleAuthorize(false)" <el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
>拒绝
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" name="SSOLogin" setup> <script lang="ts" name="SSOLogin" setup>
// import LoginFormTitle from './LoginFormTitle.vue' // TODO import LoginFormTitle from './LoginFormTitle.vue'
import { authorize, getAuthorize, paramsType, scopesType } from '@/api/login' import * as OAuth2Api from '@/api/login/oauth2'
import { LoginStateEnum, useLoginState } from './useLogin' import { LoginStateEnum, useLoginState } from './useLogin'
import type { RouteLocationNormalizedLoaded } from 'vue-router' import type { RouteLocationNormalizedLoaded } from 'vue-router'
const route = useRoute() //
const { t } = useI18n() const { currentRoute } = useRouter() //
const ssoForm = ref() // Ref
const { getLoginState, setLoginState } = useLoginState() const { getLoginState, setLoginState } = useLoginState()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.SSO)
const loginForm = reactive<{ scopes: scopesType }>({ const client = ref({
scopes: [] // scope //
name: '',
logo: ''
}) })
const params = reactive<paramsType>({ const queryParams = reactive({
// URL client_idscope // URL client_idscope
responseType: '', responseType: '',
clientId: '', clientId: '',
redirectUri: '', redirectUri: '',
state: '', state: '',
scopes: [] // query scopes: [] // query
}) // Ref
const client = ref({
//
name: '',
logo: ''
}) })
const loading = ref(false) const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // SSO
const handleAuthorize = (approved) => { const formData = reactive({
ssoForm.value.validate((valid) => { scopes: [] // scope
if (!valid) { })
return const formLoading = ref(false) //
}
loading.value = true /** 初始化授权信息 */
// checkedScopes + uncheckedScopes const init = async () => {
let checkedScopes
let uncheckedScopes
if (approved) {
//
checkedScopes = loginForm.scopes
uncheckedScopes = params.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
} else {
//
checkedScopes = []
uncheckedScopes = params.scopes
}
//
doAuthorize(false, checkedScopes, uncheckedScopes)
.then((res) => {
const href = res.data
if (!href) {
return
}
location.href = href
})
.finally(() => {
loading.value = false
})
})
}
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
return authorize(
params.responseType,
params.clientId,
params.redirectUri,
params.state,
autoApprove,
checkedScopes,
uncheckedScopes
)
}
const formatScope = (scope) => {
// scope 便
// demo "system_oauth2_scope" scope
// TODO
return t(`login.sso.${scope}`)
}
const route = useRoute()
const init = () => {
// //
if (typeof route.query.client_id === 'undefined') return 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%20user.write
// client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read // client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
params.responseType = route.query.response_type as string queryParams.responseType = route.query.response_type as string
params.clientId = route.query.client_id as string queryParams.clientId = route.query.client_id as string
params.redirectUri = route.query.redirect_uri as string queryParams.redirectUri = route.query.redirect_uri as string
params.state = route.query.state as string queryParams.state = route.query.state as string
if (route.query.scope) { if (route.query.scope) {
params.scopes = (route.query.scope as string).split(' ') queryParams.scopes = (route.query.scope as string).split(' ')
} }
// scope // scope
if (params.scopes.length > 0) { if (queryParams.scopes.length > 0) {
doAuthorize(true, params.scopes, []).then((res) => { const data = await doAuthorize(true, queryParams.scopes, [])
if (!res) { if (data) {
console.log('自动授权未通过!') location.href = data
return return
} }
location.href = res.data
})
} }
// //
getAuthorize(params.clientId).then((res) => { const data = await OAuth2Api.getAuthorize(queryParams.clientId)
client.value = res.client client.value = data.client
// scope // scope
let scopes let scopes
// 1.1 params.scope scopes // 1.1 params.scope scopes
if (params.scopes.length > 0) { if (queryParams.scopes.length > 0) {
scopes = [] scopes = []
for (const scope of res.scopes) { for (const scope of data.scopes) {
if (params.scopes.indexOf(scope.key) >= 0) { if (queryParams.scopes.indexOf(scope.key) >= 0) {
scopes.push(scope) scopes.push(scope)
} }
} }
// 1.2 params.scope 使 scopes // 1.2 params.scope 使 scopes
} else { } else {
scopes = res.scopes scopes = data.scopes
for (const scope of scopes) { for (const scope of scopes) {
params.scopes.push(scope.key) queryParams.scopes.push(scope.key)
} }
} }
// checkedScopes // checkedScopes
for (const scope of scopes) { for (const scope of scopes) {
if (scope.value) { if (scope.value) {
loginForm.scopes.push(scope.key) formData.scopes.push(scope.key)
} }
} }
})
} }
// =======SSO======
const { currentRoute } = useRouter() /** 处理授权的提交 */
// const handleAuthorize = async (approved) => {
// checkedScopes + uncheckedScopes
let checkedScopes
let uncheckedScopes
if (approved) {
//
checkedScopes = formData.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( watch(
() => currentRoute.value, () => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => { (route: RouteLocationNormalizedLoaded) => {
@ -183,5 +183,4 @@ watch(
}, },
{ immediate: true } { immediate: true }
) )
init()
</script> </script>