portal commit

This commit is contained in:
Eric
2026-02-10 16:24:14 +08:00
parent a0debc685a
commit 2c8995653a
828 changed files with 56552 additions and 99 deletions

View File

@@ -1,54 +0,0 @@
CREATE TABLE oauth2_authorization (
id varchar(100) NOT NULL COMMENT '授权记录的唯一标识符',
registered_client_id varchar(100) NOT NULL COMMENT '注册客户端的ID关联到客户端注册表',
principal_name varchar(200) NOT NULL COMMENT '授权主体的名称通常是用户ID或用户名',
authorization_grant_type varchar(100) NOT NULL COMMENT '授权类型authorization_code, client_credentials, refresh_token等',
authorized_scopes varchar(1000) DEFAULT NULL COMMENT '已授权的范围用空格分隔的scope列表',
attributes blob DEFAULT NULL COMMENT '附加属性,以二进制格式存储',
state varchar(500) DEFAULT NULL COMMENT 'OAuth2状态参数用于防止CSRF攻击',
authorization_code_value blob DEFAULT NULL COMMENT '授权码的值(加密存储)',
authorization_code_issued_at timestamp NULL DEFAULT NULL COMMENT '授权码的颁发时间',
authorization_code_expires_at timestamp NULL DEFAULT NULL COMMENT '授权码的过期时间',
authorization_code_metadata blob DEFAULT NULL COMMENT '授权码的元数据',
access_token_value blob DEFAULT NULL COMMENT '访问令牌的值(加密存储)',
access_token_issued_at timestamp NULL DEFAULT NULL COMMENT '访问令牌的颁发时间',
access_token_expires_at timestamp NULL DEFAULT NULL COMMENT '访问令牌的过期时间',
access_token_metadata blob DEFAULT NULL COMMENT '访问令牌的元数据',
access_token_type varchar(100) DEFAULT NULL COMMENT '访问令牌类型Bearer',
access_token_scopes varchar(1000) DEFAULT NULL COMMENT '访问令牌的有效范围',
oidc_id_token_value blob DEFAULT NULL COMMENT 'OIDC ID令牌的值加密存储',
oidc_id_token_issued_at timestamp NULL DEFAULT NULL COMMENT 'OIDC ID令牌的颁发时间',
oidc_id_token_expires_at timestamp NULL DEFAULT NULL COMMENT 'OIDC ID令牌的过期时间',
oidc_id_token_metadata blob DEFAULT NULL COMMENT 'OIDC ID令牌的元数据',
refresh_token_value blob DEFAULT NULL COMMENT '刷新令牌的值(加密存储)',
refresh_token_issued_at timestamp NULL DEFAULT NULL COMMENT '刷新令牌的颁发时间',
refresh_token_expires_at timestamp NULL DEFAULT NULL COMMENT '刷新令牌的过期时间',
refresh_token_metadata blob DEFAULT NULL COMMENT '刷新令牌的元数据',
user_code_value blob DEFAULT NULL COMMENT '设备流用户码的值(加密存储)',
user_code_issued_at timestamp NULL DEFAULT NULL COMMENT '设备流用户码的颁发时间',
user_code_expires_at timestamp NULL DEFAULT NULL COMMENT '设备流用户码的过期时间',
user_code_metadata blob DEFAULT NULL COMMENT '设备流用户码的元数据',
device_code_value blob DEFAULT NULL COMMENT '设备流设备码的值(加密存储)',
device_code_issued_at timestamp NULL DEFAULT NULL COMMENT '设备流设备码的颁发时间',
device_code_expires_at timestamp NULL DEFAULT NULL COMMENT '设备流设备码的过期时间',
device_code_metadata blob DEFAULT NULL COMMENT '设备流设备码的元数据',
PRIMARY KEY (id)
) COMMENT='OAuth2 授权表存储所有OAuth2和OpenID Connect的授权信息';
CREATE INDEX idx_registered_client_id ON oauth2_authorization(registered_client_id);
CREATE INDEX idx_principal_name ON oauth2_authorization(principal_name);
CREATE INDEX idx_state ON oauth2_authorization(state);
CREATE INDEX idx_access_token_expires_at ON oauth2_authorization(access_token_expires_at);
CREATE INDEX idx_refresh_token_expires_at ON oauth2_authorization(refresh_token_expires_at);
CREATE INDEX idx_authorization_code_expires_at ON oauth2_authorization(authorization_code_expires_at);
CREATE TABLE oauth2_authorization_consent (
registered_client_id varchar(100) NOT NULL COMMENT '注册客户端的ID关联到客户端注册表',
principal_name varchar(200) NOT NULL COMMENT '授权主体的名称通常是用户ID或用户名',
authorities varchar(1000) NOT NULL COMMENT '已授予的权限列表(逗号分隔的权限字符串)',
PRIMARY KEY (registered_client_id, principal_name)
) COMMENT='OAuth2授权同意表存储用户对客户端的授权同意记录';
CREATE INDEX idx_oauth2_authorization_consent_principal_name ON oauth2_authorization_consent(principal_name);
CREATE INDEX idx_oauth2_authorization_consent_client_id ON oauth2_authorization_consent(registered_client_id);

View File

@@ -4,7 +4,8 @@ VITE_APP_TITLE = IDP统一登录系统
# 开发环境配置
VITE_APP_ENV = 'development'
VITE_APP_BASE_URL=/idp-ui/
# 开发环境
VITE_APP_BASE_API = '/dev-api'
VITE_APP_BASE_API = '/idp-api2'
VITE_APP_DEFAULT_PAGE=''

View File

@@ -3,9 +3,9 @@ VITE_APP_TITLE = IDP统一登录系统
# 生产环境配置
VITE_APP_ENV = 'production'
VITE_APP_BASE_URL=/idp-ui/
# 生产环境
VITE_APP_BASE_API = '/prod-api'
VITE_APP_BASE_API = '/idp-api2'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@@ -91,7 +91,7 @@ export function logout(): Promise<AxiosResponse<any>> {
}
// 获取验证码
export function getCodeImg(): Promise<AxiosResponse<CaptchaResponse>> {
export function getCodeImg(): Promise<CaptchaResponse> {
return request({
url: '/captcha/image',
headers: {

View File

@@ -6,13 +6,11 @@ import App from './App.vue'
import router from './router'
import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.use(elementIcons)
app.component('svg-icon', SvgIcon)
app.mount('#app')

View File

@@ -13,15 +13,15 @@ const privateKey: string = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHy
'UP8iWi1Qw0Y='
// 加密
export function encrypt(txt: string): string | false {
export function encrypt(txt: string): string {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对数据进行加密
return encryptor.encrypt(txt)|| "" // 对数据进行加密
}
// 解密
export function decrypt(txt: string): string | false {
export function decrypt(txt: string): string {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey) // 设置私钥
return encryptor.decrypt(txt) // 对数据进行解密
return encryptor.decrypt(txt) || ""// 对数据进行解密
}

View File

@@ -65,7 +65,7 @@
</template>
<script setup lang="ts">
import { ref, watch, getCurrentInstance } from 'vue'
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie'
@@ -78,9 +78,9 @@ import type { FormInstance, FormRules } from 'element-plus'
interface LoginForm {
username: string
password: string
rememberMe: boolean
code: string
uuid: string
rememberMe?: boolean
code?: string
uuid?: string
}
interface CaptchaResponse {
@@ -99,7 +99,7 @@ const footerContent = defaultSettings.footerContent
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
const { proxy } = getCurrentInstance() as { proxy: { $refs: { loginRef: FormInstance } } }
const loginRef = ref<FormInstance>()
const loginForm = ref<LoginForm>({
username: "admin",
@@ -128,34 +128,15 @@ watch(route, (newRoute) => {
}, { immediate: true })
function handleLogin(): void {
proxy.$refs.loginRef.validate((valid: boolean) => {
loginRef.value?.validate((valid: boolean) => {
if (valid) {
loading.value = true
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 })
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
Cookies.set("rememberMe", String(loginForm.value.rememberMe), { expires: 30 })
} else {
// 否则移除
Cookies.remove("username")
Cookies.remove("password")
Cookies.remove("rememberMe")
}
handleRememberMe()
// 调用action的登录方法
userStore.login(loginForm.value).then(() => {
const query: QueryParams = route.query
const otherQueryParams = Object.keys(query).reduce((acc: QueryParams, cur: string) => {
if (cur !== "redirect") {
acc[cur] = query[cur]
}
return acc
}, {})
if(query.redirect){
router.push(query.redirect)
}else{
router.push({ path: redirect.value || "/", query: otherQueryParams })
}
handleRedirect(query)
}).catch(() => {
loading.value = false
// 重新获取验证码
@@ -166,13 +147,37 @@ function handleLogin(): void {
}
})
}
function handleRememberMe(): void {
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 })
Cookies.set("password", encrypt(loginForm.value.password), {expires: 30})
Cookies.set("rememberMe", String(loginForm.value.rememberMe), { expires: 30 })
} else {
Cookies.remove("username")
Cookies.remove("password")
Cookies.remove("rememberMe")
}
}
function handleRedirect(query: QueryParams): void {
if (query.redirect) {
router.push(query.redirect);
} else {
router.push({
path: redirect.value || "/",
query: Object.keys(query).reduce((acc: Record<string, any>, cur: string) => {
if (cur !== "redirect") acc[cur] = query[cur];
return acc;
}, {})
});
}
}
function getCode(): void {
getCodeImg().then((res: CaptchaResponse) => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (captchaEnabled.value && res.img && res.uuid) {
codeUrl.value = "data:image/gif;base64," + res.img
loginForm.value.uuid = res.uuid
getCodeImg().then((data: CaptchaResponse) => {
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled
if (captchaEnabled.value && data.img && data.uuid) {
codeUrl.value = "data:image/gif;base64," + data.img
loginForm.value.uuid = data.uuid
}
})
}
@@ -184,7 +189,7 @@ function getCookie(): void {
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password: password === undefined ? loginForm.value.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
}
}

View File

@@ -27,6 +27,6 @@
"~/*": ["./*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","vite/**/*.ts"],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -12,7 +12,7 @@ export default defineConfig(({ mode, command }) => {
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下vite 会假设你的应用是被部署在一个域名的根路径上
base: VITE_APP_ENV === 'production' ? '/' : '/',
base: VITE_APP_ENV === 'production' ? env?.VITE_APP_BASE_URL || '/idp/' : env?.VITE_APP_BASE_URL || '/',
plugins: createVitePlugins(env, command === 'build'),
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias