simplify oidc callback result payload

This commit is contained in:
Windfarer
2026-04-01 16:18:38 +08:00
committed by lyingbug
parent 25379f5df5
commit 1b7070c1a2
3 changed files with 58 additions and 43 deletions
+13 -11
View File
@@ -34,9 +34,9 @@
- 若用户不存在,则自动创建本地账号和默认租户;
- 最终签发 WeKnora 自己的本地 JWT(`token` / `refresh_token`)。
4. 后端不会直接把登录结果放在 query string 中,而是:
- 将结果 JSON 序列化后再做 base64url 编码;
- 将登录结果 JSON( `success`、`token`、`refresh_token` 等字段)序列化后再做 base64url 编码;
- 以 `#oidc_result=...` 的形式重定向回前端;
- 前端在 `App.vue` 中统一解析 hash,完成登录态持久化。
- 前端在 `App.vue` 中统一解析 hash,再调用 `/api/v1/auth/me` 补全用户和租户信息。
因此,**OIDC Provider 的 token 只用于后端换取用户身份,本项目真正的业务访问凭证仍然是 WeKnora 自己签发的 JWT**。
@@ -93,7 +93,9 @@ sequenceDiagram
BE-->>FE: 302 到 /#oidc_result=...
FE->>FE: App.vue 解析 hash
FE->>FE: 写入 authStore/token/tenant
FE->>BE: GET /api/v1/auth/me
BE-->>FE: { user, tenant }
FE->>FE: 写入 authStore/token/user/tenant
FE-->>U: 跳转 /platform/knowledge-bases
```
@@ -120,7 +122,7 @@ flowchart TD
M -- 是 --> O[使用现有用户]
N --> P[签发 WeKnora JWT]
O --> P
P --> Q[编码为 oidc_result]
P --> Q[编码最小必要登录结果为 oidc_result]
Q --> R[302 重定向到前端首页 hash]
```
@@ -422,12 +424,12 @@ GenerateTokens(ctx, user)
并写入本地 `auth_tokens` 存储(通过 `tokenRepo.CreateToken`)。
最终后端还会查询当前用户所属租户,并返回:
最终后端会返回 OIDC 回调载荷,其中包含:
- `user`
- `tenant`
- `token`
- `refresh_token`
- `success`
- `message`
- `is_new_user`
这一步意味着:
@@ -502,10 +504,10 @@ window.location.hash
1. base64url 解码并反序列化;
2. 如果 `response.success=true`:
- 写入 `authStore.setUser(...)`
- 写入 `authStore.setToken(...)`
- 写入 `authStore.setRefreshToken(...)`
- 写入 `authStore.setTenant(...)`
- 先写入 `authStore.setToken(...)`
- 再写入 `authStore.setRefreshToken(...)`
- 随后调用 `/api/v1/auth/me`
- 用 `/auth/me` 返回的 `user` / `tenant` 执行 `authStore.setUser(...)` 与 `authStore.setTenant(...)`
3. 最终跳转到:
```text
+44 -24
View File
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
import { MessagePlugin } from 'tdesign-vue-next'
import ManualKnowledgeEditor from '@/components/manual-knowledge-editor.vue'
import { useAuthStore } from '@/stores/auth'
import { getCurrentUser } from '@/api/auth'
// TDesign locale configs
import enUSConfig from 'tdesign-vue-next/esm/locale/en_US'
@@ -37,32 +38,50 @@ const clearOIDCCallbackState = (path = '/') => {
window.history.replaceState({}, document.title, path)
}
const persistOIDCLoginResponse = async (response: any) => {
if (response.user && response.tenant && response.token) {
authStore.setUser({
id: response.user.id || '',
username: response.user.username || '',
email: response.user.email || '',
avatar: response.user.avatar,
tenant_id: String(response.tenant.id) || '',
can_access_all_tenants: response.user.can_access_all_tenants || false,
created_at: response.user.created_at || new Date().toISOString(),
updated_at: response.user.updated_at || new Date().toISOString()
})
authStore.setToken(response.token)
if (response.refresh_token) {
authStore.setRefreshToken(response.refresh_token)
}
authStore.setTenant({
id: String(response.tenant.id) || '',
name: response.tenant.name || '',
api_key: response.tenant.api_key || '',
owner_id: response.user.id || '',
created_at: response.tenant.created_at || new Date().toISOString(),
updated_at: response.tenant.updated_at || new Date().toISOString()
})
const syncOIDCUserContext = async () => {
const currentUserResponse = await getCurrentUser()
if (!currentUserResponse.success || !currentUserResponse.data?.user || !currentUserResponse.data?.tenant) {
throw new Error(currentUserResponse.message || 'Failed to get user information')
}
const { user, tenant } = currentUserResponse.data
authStore.setUser({
id: user.id || '',
username: user.username || '',
email: user.email || '',
avatar: user.avatar,
tenant_id: String(user.tenant_id || tenant.id || ''),
can_access_all_tenants: user.can_access_all_tenants || false,
created_at: user.created_at || new Date().toISOString(),
updated_at: user.updated_at || new Date().toISOString()
})
authStore.setTenant({
id: String(tenant.id) || '',
name: tenant.name || '',
api_key: tenant.api_key || '',
owner_id: tenant.owner_id || user.id || '',
description: tenant.description,
status: tenant.status,
business: tenant.business,
storage_quota: tenant.storage_quota,
storage_used: tenant.storage_used,
created_at: tenant.created_at || new Date().toISOString(),
updated_at: tenant.updated_at || new Date().toISOString()
})
}
const persistOIDCLoginResponse = async (response: any) => {
if (!response.token) {
throw new Error(response.message || 'OIDC login failed')
}
authStore.setToken(response.token)
if (response.refresh_token) {
authStore.setRefreshToken(response.refresh_token)
}
await syncOIDCUserContext()
await nextTick()
router.replace('/platform/knowledge-bases')
}
@@ -106,6 +125,7 @@ const handleGlobalOIDCCallback = async () => {
MessagePlugin.error(response.message || 'OIDC login failed')
} catch (error: any) {
console.error('Global OIDC callback handling failed:', error)
authStore.logout()
clearOIDCCallbackState('/login')
await router.replace('/login')
MessagePlugin.error(error.message || 'OIDC login failed')
+1 -8
View File
@@ -306,16 +306,9 @@ func (s *userService) LoginWithOIDC(ctx context.Context, code, redirectURI strin
return nil, fmt.Errorf("failed to generate local tokens: %w", err)
}
tenant, err := s.tenantService.GetTenantByID(ctx, user.TenantID)
if err != nil {
logger.Warnf(ctx, "Failed to get tenant info after OIDC login: %v", err)
}
return &types.OIDCCallbackResponse{
Success: true,
Message: "Login successful",
User: user,
Tenant: tenant,
Message: "登录成功",
Token: accessToken,
RefreshToken: refreshToken,
IsNewUser: isNewUser,