多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

WeKnora API 认证与用户体系完全指南:注册、登录、OIDC、令牌刷新与邀请入会实战

WeKnora API 认证与用户体系完全指南:注册、登录、OIDC、令牌刷新与邀请入会实战 WeKnora API 认证与用户体系完全指南注册、登录、OIDC、令牌刷新与邀请入会实战【免费下载链接】WeKnoraOpen-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.项目地址: https://gitcode.com/GitHub_Trending/we/WeKnora本文以 WeKnora 的/api/v1/auth/*认证与/api/v1/me/invitations邀请收件箱接口为骨架系统讲解从自助注册、邮箱密码登录、OIDC 企业登录、refresh token 换发到空间切换、密码策略与邀请入会的完整调用链。文中所有接口字段、响应结构与实现细节均以仓库源码为准并给出对应的 Handler 与路由注册位置读者可直接复制 curl 示例进行联调也能深入理解背后的认证中间件与运行时配置解析机制。一、认证接口总览与鉴权分级WeKnora 的认证与用户相关接口统一挂在/api/v1前缀之下路由注册集中在 internal/router/routes_auth_tenant.go 的RegisterAuthRoutes与 同文件 的RegisterMyInvitationRoutes两个函数中并由 internal/router/router.go 在 v1 分组上统一挂载。接口按鉴权强度分为三类等级说明接口示例免认证公开无需任何令牌即可调用部分带 IP 限流register、login、refresh、oidc/*、auto-setup、config需登录无角色下限经过认证中间件即可不要求空间角色switch-tenant、validate、logout、me/preferences、change-password、me/invitations/*需登录 API key 策略认证中间件之后叠加apiKeyAny()等守卫auth/me任意有效 API key 均可调用从源码路由表可以看到除 GET /auth/me 显式通过g.apiKeyRoute(..., apiKeyAny(), ...)放开了任意 API key 之外其余认证路由均为 JWT Bearer 语义。auth/me之所以对 API key 放开是因为 Chat 客户端与 MCP 服务需要通过它回答“我是谁”若保持默认拒绝会导致作用域 key 在此处收到 403。二、注册自助注册与注册模式开关2.1 POST /api/v1/auth/register —— 自助注册用途在自助注册模式self_serve下创建新用户账号。免认证。Handlerinternal/handler/auth.go 的Register。请求体字段字段类型必填说明usernamestring是binding:required用户名emailstring是binding:required邮箱passwordstring是binding:required密码需满足运行时密码策略tenant_provisioningstring否空间开通策略默认由default_tenant_mode解析响应201 {success:true,message:...,user:{User}}curl -X POST $BASE/api/v1/auth/register -H Content-Type: application/json \ -d {username:alice,email:aex.com,password:secret123}注册是否被允许由运行时注册模式决定其解析优先级为DBsystem_settings 配置文件cfg.Auth.RegistrationMode 硬编码默认self_serve见 internal/handler/auth.go 的resolveRegistrationMode。当模式为invite_only时Register直接返回 403 Registration is invite-only注册入口从 UI 隐藏与接口拒绝始终保持一致。2.2 注册模式的配置来源配置项定义在 internal/config/config.go 的AuthConfigauth.registration_modeself_serve默认任何人可注册自动创建空间且注册人成为 Owner或invite_only关闭公开注册仅可走邀请流程auth.default_tenant_modecreate_personal默认一人一空间或tenantless只建身份等待邀请或显式自建空间auth.complex_password_enabled是否开启复杂密码模式。值得注意的历史兼容逻辑旧环境变量DISABLE_REGISTRATIONtrue会在配置加载阶段被等价提升为invite_only见 internal/config/config.go 与测试 internal/config/auth_legacy_env_test.go即老配置无需改动即可平滑迁移到新模式。2.3 GET /api/v1/auth/config —— 查询注册模式前端在应用加载时读取该接口以决定是否展示注册 Tab 与使用哪套密码校验规则。免认证响应200 {success:true,registration_mode:self_serve|invite_only,complex_password_enabled:false}。curl $BASE/api/v1/auth/config实现上 GetAuthConfig 与Register的闸门共享同一个resolveRegistrationMode数据源因此“UI 隐藏按钮”的信号与“API 拒绝请求”的强制信号永远不可能不一致。三、登录与令牌生命周期3.1 POST /api/v1/auth/login —— 邮箱密码登录用途邮箱密码登录换取访问令牌与刷新令牌。免认证。Handlerinternal/handler/auth.go 的Login。字段类型必填说明emailstring是binding:required邮箱passwordstring是binding:required密码响应200 {success:true,user:{...},active_tenant:{...},memberships:[...],token:...,refresh_token:...}curl -X POST $BASE/api/v1/auth/login -H Content-Type: application/json -d {email:aex.com,password:secret123}3.2 POST /api/v1/auth/refresh —— 刷新令牌访问令牌过期后用 refresh token 换取新的访问令牌与新的 refresh token。免认证。字段refreshTokenbinding:required。响应200 {success:true,access_token:...,refresh_token:...}curl -X POST $BASE/api/v1/auth/refresh -H Content-Type: application/json -d {refreshToken:rt}3.3 GET /api/v1/auth/validate 与 POST /api/v1/auth/logoutGET /auth/validate校验当前 Bearer token 是否有效。需登录无空间也可调用。响应200 {success:true,message:Token is valid,user:{UserInfo}}。POST /auth/logout登出。无请求体需登录。从源码看 Logout 会解析Authorization: Bearer ...头并撤销该用户全部未过期会话保证登出后 refresh token 也不能继续工作。响应200 {success:true,message:Logout successful}。curl $BASE/api/v1/auth/validate -H Authorization: Bearer $TOKEN curl -X POST $BASE/api/v1/auth/logout -H Authorization: Bearer $TOKEN3.4 POST /api/v1/auth/auto-setup —— Lite 桌面版一键初始化用途本地 / Lite 场景自动建号建空间免去手动注册登录。免认证无请求体且仅在 lite 版本生效源码中非 lite 直接返回 403见 internal/handler/auth.go。首次调用会以adminweknora.local创建默认用户密码为随机 24 字节、用户名为随机前缀并自动开通空间后续启动则直接为该用户签发令牌。响应结构与 Login 相同user/active_tenant/memberships/token/refresh_token。curl -X POST $BASE/api/v1/auth/auto-setup四、OIDC 企业单点登录四件套OIDC 相关配置定义在 internal/config/config.go 的OIDCAuthConfig包括enable、issuer_url、discovery_url、provider_display_name、client_id、client_secretJSON 输出时被显式屏蔽标记为json:-、各端点地址与user_info_mapping。四组接口全部免认证4.1 GET /api/v1/auth/oidc/config查询 OIDC 是否启用及提供方显示名供前端决定是否渲染 OIDC 登录入口。响应200 {success:true,enabled:bool,provider_display_name:...}。4.2 GET /api/v1/auth/oidc/url获取 OIDC 授权跳转 URL。查询参数redirect_uri必填回调地址。响应200 {success:true,authorization_url:...,nonce:...}。curl $BASE/api/v1/auth/oidc/url?redirect_urihttps://app.example.com/callback实现细节生成的nonce会通过 setOIDCNonceCookie 绑定到当前浏览器weknora_oidc_nonceCookie有效期 600 秒防止攻击者把自己的授权码回放到受害者的回调中Cookie 仅在 TLS 或X-Forwarded-Proto: https时打Secure标记。4.3 GET /api/v1/auth/oidc/start免登录、无需前端 JS 介入直接返回302与指向 IdP 的Location适合企业门户直接放一个链接发起 SSO 登录。回调地址由请求自身 originscheme host构造为/api/v1/auth/oidc/callback见 oidcCallbackURL。回调后的登录结果与原 OIDC 链路完全一致。curl -i $BASE/api/v1/auth/oidc/start4.4 GET /api/v1/auth/oidc/callbackOIDC 授权回调浏览器重定向进入。查询参数code、state、error、error_description均由 IdP 带回。成功302 重定向到前端携带#oidc_resultbase64urlpayload 为登录结果 JSON 的 RawURLEncoding失败携带#oidc_error...及可选的#oidc_error_description...。curl -i $BASE/api/v1/auth/oidc/callback?codexxxstateyyy回调时会校验 state 中签名的 nonce 与浏览器 Cookie 中的 nonce 一致一次性使用校验通过即清除 Cookie随后用code完成授权码交换并落库用户信息。整个start → url → callback的 302 与 nonce 绑定逻辑见 internal/handler/auth.go。五、空间切换与个人偏好5.1 POST /api/v1/auth/switch-tenant —— 切换活跃空间用途为当前用户在目标空间重新签发访问令牌。需登录无空间也可调用。Handlerinternal/handler/auth.go 的SwitchTenant。字段类型必填说明tenant_iduint64是binding:required目标空间 IDrefresh_tokenstring否用于换发新 token响应200结构与 Login 相同。curl -X POST $BASE/api/v1/auth/switch-tenant -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json -d {tenant_id:2}三个值得注意的行为约定源码注释与实现双重确认换签会更新账号级偏好成功换签会把目标空间写入「最近活跃租户」下次登录密码/OIDC/换设备与 refresh 都回到该空间refresh JWT 不含tenant_id因此偏好写入失败则整次换签失败、不会发出新 tokenAPI 客户端无需再补发PUT /auth/me/preferences偏好是账号级的一次换签会改变该用户所有设备的下次登录/refresh 落点Web UI 切空间不走本接口。5.2 GET /api/v1/auth/me 与 PUT /api/v1/auth/me/preferencesGET /auth/me查询当前调用者身份。需登录API key 亦可路由策略apiKeyAny()。响应200 {success:true,data:{user:{UserInfo},tenant:{TenantResponse},memberships:[...],tenant_required:bool,capabilities:{can_create_tenant:bool,auto_accept_invitation:bool}}}。实现上 GetCurrentUser 会优先从认证上下文中取活跃空间X-Tenant-ID解析出的租户而非账号注册时的 home 租户避免前端在页面刷新后角色信息错位。curl $BASE/api/v1/auth/me -H X-API-Key: $API_KEYPUT /auth/me/preferences按 PATCH 语义合并更新个人偏好仅覆盖请求体出现的字段。字段last_active_tenant_id*uint64正整数设置/替换0清除下次登录回 home省略则不改。数据存放在users.preferencesJSON跨设备/浏览器自动同步。curl -X PUT $BASE/api/v1/auth/me/preferences -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json -d {last_active_tenant_id:2}六、密码策略与修改密码6.1 密码策略的运行时解析密码策略由GET /auth/config提供开关最终由 internal/application/service/password_policy.go 的ValidatePasswordPolicy执行默认策略8–32 位按 rune 计必须包含字母大小写任一与数字复杂模式auth.complex_password_enabledtrue必须同时包含大写字母、小写字母、数字与特殊字符特殊字符白名单为!#$%^*()_-[]{}|;:,.?。这里特别提醒不能只根据请求结构体的binding长度标签推导完整校验规则——例如register-by-invite结构体上写的是binding:required,min6真实规则却由ValidatePasswordPolicy统一执行 8–32 位策略两者并不等价。策略解析的优先级为 DBsystem_settings 环境变量WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLEDcfg.Auth false见 ResolveComplexPasswordEnabled。6.2 POST /api/v1/auth/change-password —— 修改密码需登录。Handlerinternal/handler/auth.go 的ChangePassword。字段类型必填说明old_passwordstring是binding:required旧密码new_passwordstring是binding:required新密码8–32 位字母数字复杂模式另需大小写和特殊字符响应200 {success:true,message:Password changed successfully}curl -X POST $BASE/api/v1/auth/change-password -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json -d {old_password:old,new_password:NewPass123!}三个错误语义均返回 400通过details字段透出机器可读 token便于 SPA 本地化invalid_old_password旧密码不正确password_policy新密码不满足策略same_password新旧密码相同。实现上先校验旧密码、再校验新密码策略避免用复杂度错误掩盖“旧密码错误”的真实原因成功后撤销全部会话需要重新登录。对应哨兵错误定义在 internal/application/service/user.go行为由 internal/application/service/user_auth_token_test.go 覆盖验证。七、邀请入会从匿名注册到登录后加入WeKnora 提供两条邀请入会路径一条免认证共享链接注册新账号一条需登录将邀请应用到现有账号。7.1 POST /api/v1/auth/register-by-invite —— 共享链接注册用途通过 Owner 生成的共享邀请链接 token 注册并自动加入空间。免认证IP 限流 30 次/分钟。Handlerinternal/handler/auth_register_by_invite.go 的RegisterByInvite。字段类型必填说明tokenstring是binding:required邀请 tokenemailstring是binding:required,email邮箱自填不与 token 预绑定usernamestring是binding:required用户名passwordstring是binding:required,min6密码真实策略以ValidatePasswordPolicy为准响应201同 Loginuser/active_tenant/memberships/token/refresh_token。curl -X POST $BASE/api/v1/auth/register-by-invite -H Content-Type: application/json \ -d {token:invite_token,email:aex.com,username:alice,password:secret123}关键行为源码可证该接口不受invite_only闸门拦截——token 本身就是授权凭证见 internal/handler/auth_register_by_invite.go 注释邮箱已存在时返回 409 并提示“请登录后用/me/invitations加入”避免静默成功Lookup 与 Accept 之间存在“链接被撤销”的竞态若 Accept 失败新账号会被恢复为无空间状态甚至删除半成品账号再返回 410。7.2 POST /api/v1/auth/invitations/lookup —— 注册前预览用途匿名查询邀请 token 对应的空间信息注册页渲染“某人邀请你加入某空间”的上下文。免认证同样走 IP 限流。设计细节token 放在请求体中而非 URL 路径避免明文 token 落入访问日志、浏览器历史与 tracing 链路见 internal/handler/auth_register_by_invite.go。字段类型必填说明tokenstring是binding:required邀请 token响应200{success:true,data:{tenant_id,tenant_name,role,expires_at}}token 无效/过期/撤销统一返回410不泄露被盗 token 曾对应哪个槽位。curl -X POST $BASE/api/v1/auth/invitations/lookup -H Content-Type: application/json -d {token:invite_token}7.3 公开端点的 IP 限流实现register-by-invite与invitations/lookup共享同一个进程级滑动窗口限流器实现见 internal/middleware/auth_public_ratelimit.go窗口60 秒配额每 IP 每窗口 30 次publicAuthRateLimitWindow/publicAuthRateLimitMax超限返回 429ErrTooManyRequests与其余错误走同一套 AppError 中间件限流器为进程内存实现适用于当前部署形态若未来水平扩展认证面可替换为internal/ratelimit下的 Redis 版路由层通过 PublicAuthRateLimit() 中间件挂在两个端点前见 internal/router/routes_auth_tenant.go两个端点共享总预算语义直观。八、我的邀请收件箱/api/v1/me/invitations服务层保证“仅被邀请人可接受/拒绝”因此这些接口无角色下限——无空间的新用户也可调用。Handlerinternal/handler/tenant_invitation.go路由见 internal/router/routes_auth_tenant.go。8.1 GET /api/v1/me/invitations列出发给我的邀请。查询参数include_terminalbool可选为true时包含已完结已处理/已过期的邀请。响应200{success:true,data:{invitations:[TenantInvitationResponse],total:N}}curl $BASE/api/v1/me/invitations -H Authorization: Bearer $TOKEN8.2 GET /api/v1/me/invitations/pending-count待处理邀请计数供头像角标轻量轮询每次只传一个数字不拖全量列表。响应200{success:true,data:{pending_count:N}}。curl $BASE/api/v1/me/invitations/pending-count -H Authorization: Bearer $TOKEN8.3 POST /api/v1/me/invitations/:inv_id/accept接受邀请服务端同时写入tenant_members行。路径参数inv_id为邀请 ID无请求体。若当前用户是无空间tenantless用户首个接受的邀请会成为其默认空间源码见 internal/handler/tenant_invitation.go。响应200{success:true,data:{membership:{tenant_id,role,status,joined_at}}}curl -X POST $BASE/api/v1/me/invitations/12/accept -H Authorization: Bearer $TOKEN8.4 POST /api/v1/me/invitations/:inv_id/decline拒绝邀请不创建成员行。响应200{success:true}。curl -X POST $BASE/api/v1/me/invitations/12/decline -H Authorization: Bearer $TOKEN错误映射accept/decline 通用invitation not found→ 404非本人邀请 → 403非 pending 态 → 409ErrInvitationNotPending已过期 → 409ErrInvitationExpired。8.5 POST /api/v1/me/invitations/accept-by-token已登录用户用共享链接 token 直接加入空间不创建新账号对已是成员的用户幂等。需登录仅操作当前用户。curl -X POST $BASE/api/v1/me/invitations/accept-by-token \ -H Authorization: Bearer $TOKEN -H Content-Type: application/json \ -d {token:invite-token}空 token 返回 400无效/过期/撤销的链接返回410与LookupInvitationByToken的语义保持一致成功响应{success:true,data:{membership:{tenant_id,role,status,joined_at},tenant_name}}前端据此切换空间无空间用户同样会把首个加入的空间设为默认空间。九、快速联调清单curl 视角综合全篇一次完整的“注册 → 登录 → 加入空间 → 切换空间 → 换发令牌 → 登出”链路如下export BASEhttp://localhost:8080 # 1. 查询注册模式与密码策略免认证 curl $BASE/api/v1/auth/config # 2. 自助注册免认证 curl -X POST $BASE/api/v1/auth/register -H Content-Type: application/json \ -d {username:alice,email:aex.com,password:secret123} # 3. 登录拿 token 与 refresh_token免认证 curl -X POST $BASE/api/v1/auth/login -H Content-Type: application/json \ -d {email:aex.com,password:secret123} export TOKENaccess_token export RTrefresh_token # 4. 校验令牌需登录 curl $BASE/api/v1/auth/validate -H Authorization: Bearer $TOKEN # 5. 切换活跃空间需登录 curl -X POST $BASE/api/v1/auth/switch-tenant -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json -d {tenant_id:2} # 6. 换发令牌免认证 curl -X POST $BASE/api/v1/auth/refresh -H Content-Type: application/json -d {\refreshToken\:\$RT\} # 7. 登出需登录撤销全部会话 curl -X POST $BASE/api/v1/auth/logout -H Authorization: Bearer $TOKEN十、实现参考索引路由注册internal/router/routes_auth_tenant.goRegisterAuthRoutes、同文件RegisterMyInvitationRoutes、internal/router/router.go认证 Handlerinternal/handler/auth.go注册/登录/OIDC/换签/刷新/偏好/改密/auto-setup邀请注册 Handlerinternal/handler/auth_register_by_invite.go邀请收件箱 Handlerinternal/handler/tenant_invitation.go密码策略internal/application/service/password_policy.go错误哨兵见 internal/application/service/user.go认证配置结构internal/config/config.goAuthConfig/OIDCAuthConfig测试见 internal/config/auth_legacy_env_test.go公开端点限流internal/middleware/auth_public_ratelimit.go变更密码行为测试internal/application/service/user_auth_token_test.go。【免费下载链接】WeKnoraOpen-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.项目地址: https://gitcode.com/GitHub_Trending/we/WeKnora创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表