Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
276e82c5
authored
Apr 09, 2023
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
REVIEW 单点登录界面
parent
0f0ba8b8
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
147 additions
and
181 deletions
+147
-181
src/api/login/index.ts
+0
-55
src/api/login/oauth2/index.ts
+41
-0
src/api/login/types.ts
+0
-14
src/locales/zh-CN.ts
+0
-6
src/types/auto-components.d.ts
+1
-0
src/views/Login/components/SSOLogin.vue
+105
-106
No files found.
src/api/login/index.ts
View file @
276e82c5
import
request
from
'@/config/axios'
import
{
getRefreshToken
}
from
'@/utils/auth'
import
type
{
UserLoginVO
}
from
'./types'
import
{
service
}
from
'@/config/axios/service'
export
interface
CodeImgResult
{
captchaOnOff
:
boolean
img
:
string
uuid
:
string
}
export
interface
SmsCodeVO
{
mobile
:
string
...
...
@@ -74,51 +67,3 @@ export const getCode = (data) => {
export
const
reqCheck
=
(
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'
})
}
src/api/login/oauth2/index.ts
0 → 100644
View file @
276e82c5
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
)
}
})
}
src/api/login/types.ts
View file @
276e82c5
...
...
@@ -26,17 +26,3 @@ export type UserVO = {
loginIp
:
string
loginDate
:
string
}
export
type
UserInfoVO
=
{
permissions
:
[]
roles
:
[]
user
:
{
avatar
:
string
id
:
number
nickname
:
string
}
}
export
type
TentantNameVO
=
{
name
:
string
}
src/locales/zh-CN.ts
View file @
276e82c5
...
...
@@ -129,12 +129,6 @@ export default {
btnMobile
:
'手机登录'
,
btnQRCode
:
'二维码登录'
,
qrcode
:
'扫描二维码登录'
,
sso
:
{
user
:
{
read
:
'访问你的个人信息'
,
write
:
'修改你的个人信息'
}
},
btnRegister
:
'注册'
,
SmsSendMsg
:
'验证码已发送'
},
...
...
src/types/auto-components.d.ts
View file @
276e82c5
...
...
@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
Descriptions
:
typeof
import
(
'./../components/Descriptions/src/Descriptions.vue'
)[
'default'
]
Dialog
:
typeof
import
(
'./../components/Dialog/src/Dialog.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'
]
Editor
:
typeof
import
(
'./../components/Editor/src/Editor.vue'
)[
'default'
]
ElBadge
:
typeof
import
(
'element-plus/es'
)[
'ElBadge'
]
...
...
src/views/Login/components/SSOLogin.vue
View file @
276e82c5
<
template
>
<
!-- 表单 --
>
<div
v-show=
"getShow"
class=
"form-cont"
>
<
!--
<LoginFormTitle
style=
"width: 100%"
/>
--
>
<
div
v-show=
"ssoVisible"
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-tab-pane
:label=
"
client.name
"
name=
"uname"
/>
</el-tabs>
<div>
<el-form
ref=
"ssoForm"
:model=
"loginForm
"
class=
"login-form"
>
<el-form
:model=
"formData
"
class=
"login-form"
>
<!-- 授权范围的选择 -->
此第三方应用请求获得以下权限:
<el-form-item
prop=
"scopes"
>
<el-checkbox-group
v-model=
"
loginForm
.scopes"
>
<el-checkbox-group
v-model=
"
formData
.scopes"
>
<el-checkbox
v-for=
"scope in
p
arams.scopes"
v-for=
"scope in
queryP
arams.scopes"
:key=
"scope"
:label=
"scope"
style=
"display: block; margin-bottom: -10px"
>
{{
formatScope
(
scope
)
}}
>
{{
formatScope
(
scope
)
}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 下方的登录按钮 -->
<el-form-item
style=
"width: 100%
"
>
<el-form-item
class=
"w-1/1
"
>
<el-button
:loading=
"loading"
size=
"small"
style=
"width: 60%"
:loading=
"formLoading"
class=
"w-6/10"
type=
"primary"
@
click
.
prevent=
"handleAuthorize(true)"
>
<span
v-if=
"!
l
oading"
>
同意授权
</span>
<span
v-if=
"!
formL
oading"
>
同意授权
</span>
<span
v-else
>
授 权 中...
</span>
</el-button>
<el-button
size=
"small"
style=
"width: 36%"
@
click
.
prevent=
"handleAuthorize(false)"
>
拒绝
</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"
name=
"SSOLogin"
setup
>
// import LoginFormTitle from './LoginFormTitle.vue' // TODO 艿艿你看看要不要这个表头
import
{
authorize
,
getAuthorize
,
paramsType
,
scopesType
}
from
'@/api/login
'
import
LoginFormTitle
from
'./LoginFormTitle.vue'
import
*
as
OAuth2Api
from
'@/api/login/oauth2
'
import
{
LoginStateEnum
,
useLoginState
}
from
'./useLogin'
import
type
{
RouteLocationNormalizedLoaded
}
from
'vue-router'
const
{
t
}
=
useI18n
()
const
ssoForm
=
ref
()
// 表单Ref
const
route
=
useRoute
()
// 路由
const
{
currentRoute
}
=
useRouter
()
// 路由
const
{
getLoginState
,
setLoginState
}
=
useLoginState
()
const
getShow
=
computed
(()
=>
unref
(
getLoginState
)
===
LoginStateEnum
.
SSO
)
const
loginForm
=
reactive
<
{
scopes
:
scopesType
}
>
({
scopes
:
[]
// 已选中的 scope 数组
const
client
=
ref
({
// 客户端信息
name
:
''
,
logo
:
''
})
const
params
=
reactive
<
paramsType
>
({
const
queryParams
=
reactive
({
// URL 上的 client_id、scope 等参数
responseType
:
''
,
clientId
:
''
,
redirectUri
:
''
,
state
:
''
,
scopes
:
[]
// 优先从 query 参数获取;如果未传递,从后端获取
})
// 表单Ref
const
client
=
ref
({
// 客户端信息
name
:
''
,
logo
:
''
})
const
loading
=
ref
(
false
)
const
handleAuthorize
=
(
approved
)
=>
{
ssoForm
.
value
.
validate
((
valid
)
=>
{
if
(
!
valid
)
{
return
}
loading
.
value
=
true
// 计算 checkedScopes + uncheckedScopes
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
=
()
=>
{
const
ssoVisible
=
computed
(()
=>
unref
(
getLoginState
)
===
LoginStateEnum
.
SSO
)
// 是否展示 SSO 登录的表单
const
formData
=
reactive
({
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
p
arams
.
responseType
=
route
.
query
.
response_type
as
string
p
arams
.
clientId
=
route
.
query
.
client_id
as
string
p
arams
.
redirectUri
=
route
.
query
.
redirect_uri
as
string
p
arams
.
state
=
route
.
query
.
state
as
string
queryP
arams
.
responseType
=
route
.
query
.
response_type
as
string
queryP
arams
.
clientId
=
route
.
query
.
client_id
as
string
queryP
arams
.
redirectUri
=
route
.
query
.
redirect_uri
as
string
queryP
arams
.
state
=
route
.
query
.
state
as
string
if
(
route
.
query
.
scope
)
{
p
arams
.
scopes
=
(
route
.
query
.
scope
as
string
).
split
(
' '
)
queryP
arams
.
scopes
=
(
route
.
query
.
scope
as
string
).
split
(
' '
)
}
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
if
(
p
arams
.
scopes
.
length
>
0
)
{
doAuthorize
(
true
,
params
.
scopes
,
[]).
then
((
res
)
=>
{
if
(
!
res
)
{
console
.
log
(
'自动授权未通过!'
)
if
(
queryP
arams
.
scopes
.
length
>
0
)
{
const
data
=
await
doAuthorize
(
true
,
queryParams
.
scopes
,
[])
if
(
data
)
{
location
.
href
=
data
return
}
location
.
href
=
res
.
data
})
}
// 获取授权页的基本信息
getAuthorize
(
params
.
clientId
).
then
((
res
)
=>
{
client
.
value
=
res
.
client
const
data
=
await
OAuth2Api
.
getAuthorize
(
queryParams
.
clientId
)
client
.
value
=
data
.
client
// 解析 scope
let
scopes
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes
if
(
p
arams
.
scopes
.
length
>
0
)
{
if
(
queryP
arams
.
scopes
.
length
>
0
)
{
scopes
=
[]
for
(
const
scope
of
res
.
scopes
)
{
if
(
p
arams
.
scopes
.
indexOf
(
scope
.
key
)
>=
0
)
{
for
(
const
scope
of
data
.
scopes
)
{
if
(
queryP
arams
.
scopes
.
indexOf
(
scope
.
key
)
>=
0
)
{
scopes
.
push
(
scope
)
}
}
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
}
else
{
scopes
=
res
.
scopes
scopes
=
data
.
scopes
for
(
const
scope
of
scopes
)
{
p
arams
.
scopes
.
push
(
scope
.
key
)
queryP
arams
.
scopes
.
push
(
scope
.
key
)
}
}
// 生成已选中的 checkedScopes
for
(
const
scope
of
scopes
)
{
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
(
()
=>
currentRoute
.
value
,
(
route
:
RouteLocationNormalizedLoaded
)
=>
{
...
...
@@ -183,5 +183,4 @@ watch(
},
{
immediate
:
true
}
)
init
()
</
script
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment