# Beacon SSO SDK (/docs/beacon-sso) Beacon SSO SDK [#beacon-sso-sdk] Beacon SSO SDK 为现代后端服务提供统一认证能力,支持多种语言技术栈,帮助快速集成 OAuth2 授权码流程、用户管理和商户服务。 选择你的技术栈 [#选择你的技术栈] 面向 Gin 框架的轻量级 OAuth2/SSO SDK,提供 OAuth2 授权码流程、gRPC 服务客户端和认证中间件。 基于 Spring Boot 3 的 OAuth2/SSO SDK,提供自动配置、Filter 认证、AOP 切面和可选 gRPC 服务。 核心能力 [#核心能力] | 能力 | 说明 | | ---------------- | ------------------------- | | **OAuth2 授权码流程** | 完整的 State + PKCE 防护机制 | | **gRPC 服务客户端** | 认证、用户、商户等核心服务 | | **认证中间件/过滤器** | 开箱即用的 Token 验证 | | **缓存策略** | 灵活的缓存方案(Redis / Caffeine) | # AI 集成 (/docs/guide/ai-integration) AI 集成 [#ai-集成] 本站原生支持 LLM(大语言模型)友好协议,你可以让 AI 助手直接读取文档内容,获得更精准的技术支持。 本文档站所有 LLM 可访问资源的根路径为: ``` https://beacon.api-fy.cn ``` 后续所有路径示例均为**相对路径**,使用时请拼接上方根路径。 使用方式 [#使用方式] 方式一:文档索引 [#方式一文档索引] 访问 `/llms.txt` 获取所有文档页面的索引列表,然后按需请求单页内容。 ``` https://beacon.api-fy.cn/llms.txt ``` 方式二:单页 MDX 模式 [#方式二单页-mdx-模式] 每个文档页面都支持 MDX 原始内容获取,在路径后加 `.mdx` 前缀即可: ``` https://beacon.api-fy.cn/llms.mdx/docs/beacon-sso/go-sdk/quick-start https://beacon.api-fy.cn/llms.mdx/docs/beacon-sso/java-sdk/quick-start ``` 推荐方式(方式一 + 方式二结合) [#推荐方式方式一--方式二结合] **先读索引,再按需拉取单页内容**——这是最高效的使用策略: 1. 访问 `/llms.txt` 获取所有页面的索引列表 2. 从索引中定位与问题相关的页面路径 3. 访问 `/llms.mdx/` 获取该页面的完整内容 4. 基于内容回答问题 这样可以避免一次性加载全量文档,让 AI 只获取真正需要的内容。 Agent Prompt 模板 [#agent-prompt-模板] 以下 prompt 可直接复制到任意支持网络请求的 AI Agent 中使用: ```markdown 请按以下步骤获取锋翎文档并回答我的问题: 1. 获取文档索引:https://beacon.api-fy.cn/llms.txt 2. 从索引中找到与我的问题相关的页面路径 3. 获取该页面内容:https://beacon.api-fy.cn/llms.mdx/<找到的路径> 4. 基于文档内容回答:[你的问题] ``` 将上述 prompt 复制到 Claude、ChatGPT、Cursor 或其他支持网络请求的 AI 工具中,替换最后一行为你的具体问题,即可自动获取并分析文档内容。 相关链接 [#相关链接] * [LLM 协议文件](/llms.txt) - 文档索引 * [完整文档](/llms-full.txt) - 所有内容合并 * [页面操作](./navigation) - 每页可复制 MDX 源码 参考资料 [#参考资料] * **Beacon SSO 全局文档**: [https://beacon.api-fy.cn/llms.txt](https://beacon.api-fy.cn/llms.txt) * **具体路径查询**: `https://beacon.api-fy.cn/llms.mdx/` # 入门指南 (/docs/guide) 入门指南 [#入门指南] 欢迎使用锋翎文档!本指南将帮助你快速了解如何使用本站获取所需信息。 关于 Phalanx [#关于-phalanx] Phalanx 是一套跨语言的后端服务组件库,旨在提供统一风格的 API 设计和开发体验。目前支持: 快速开始 [#快速开始] 选择你的语言 [#选择你的语言] 根据你的技术栈选择对应的文档: * **Go 开发者**:前往 [Go 快速开始](../beacon-sso/go-sdk/quick-start) 了解如何在 Gin 项目中集成 * **Java 开发者**:前往 [Java 快速开始](../beacon-sso/java-sdk/quick-start) 了解如何在 Spring Boot 项目中集成 核心功能 [#核心功能] Beacon SSO 在两种语言中提供了相似的核心功能: | 功能 | 说明 | | ------------- | ------------------ | | **OAuth2 授权** | 完整的 OAuth2 授权码流程支持 | | **gRPC 客户端** | 高效的服务间通信 | | **缓存** | 灵活的缓存策略,降低鉴权开销 | 反馈与贡献 [#反馈与贡献] 如果你发现文档有误或有改进建议: * **GitHub**:[xiaolfeng](https://github.com/xiaolfeng) 提交 Issue 或 PR * **网站**:[筱锋的博客](https://www.x-lf.com) 留言 *** # 文档导航 (/docs/guide/navigation) 文档导航 [#文档导航] 了解如何高效浏览和使用锋翎文档站。 侧边栏 [#侧边栏] 左侧侧边栏提供了完整的文档目录结构,你可以: * 点击展开/折叠分类 * 使用搜索功能快速定位(按 `Ctrl+K` 或 `Cmd+K`) 侧边栏特性 [#侧边栏特性] | 特性 | 说明 | | -------- | ---------------------------------- | | **折叠记忆** | 侧边栏会记住你的折叠状态,下次访问时保持一致 | | **快速跳转** | 使用键盘快捷键 `Ctrl+K` / `Cmd+K` 快速搜索并跳转 | | **层级导航** | 支持多级目录结构,清晰展示文档层次 | 页面内导航 [#页面内导航] 每个文档页面右侧会显示当前页面的目录大纲(TOC),方便跳转到感兴趣的章节。 大纲特性 [#大纲特性] * **自动生成**:根据标题层级自动生成 * **高亮当前**:滚动时自动高亮当前阅读的章节 * **点击跳转**:点击任意条目快速定位 页面操作 [#页面操作] 每个文档页面顶部提供了便捷操作按钮: 复制原始内容 [#复制原始内容] 点击复制按钮将当前页面的 MDX 源码复制到剪贴板,方便: * 粘贴到 AI 对话中 * 保存到本地笔记 * 分享给他人 在 AI 平台打开 [#在-ai-平台打开] 一键跳转到 AI 平台并自动带上文档链接,支持: * **ChatGPT**:跳转到 ChatGPT 并附带文档链接 * **Claude**:跳转到 Claude 并附带文档链接 * **其他平台**:更多 AI 平台持续添加中 如果你想在 AI 平台中讨论某个具体页面,直接点击「在 AI 平台打开」按钮,比手动复制链接更方便! 搜索功能 [#搜索功能] 全局搜索 [#全局搜索] 按 `Ctrl+K`(Windows/Linux)或 `Cmd+K`(macOS)打开搜索面板: * 支持模糊搜索 * 搜索标题和内容 * 显示匹配结果预览 搜索技巧 [#搜索技巧] | 技巧 | 示例 | | ---- | ---------------- | | 精确匹配 | `"BaseResponse"` | | 模块搜索 | `beacon-sso` | | 功能搜索 | `雪花算法` | *** # 架构设计 (/docs/beacon-sso/go-sdk/architecture) 架构设计 [#架构设计] Beacon SSO SDK 采用分层架构设计,与 bamboo-base-go 框架深度集成。 整体架构 [#整体架构] 依赖注入模式 [#依赖注入模式] SDK 采用 bamboo-base-go 的 Register 依赖注入模式: ```go // startup/startup.go func NewStartupConfig(exclude ...string) []xRegNode.RegNodeList ``` 该函数聚合了以下注册节点: | 节点名称 | 说明 | | ------------------ | -------------------------------- | | `oAuthConfig` | OAuth2 核心配置(ClientID、Endpoint 等) | | `oAuthRedirectURI` | OAuth2 重定向地址 | | `ssoClient` | SsoClient gRPC 客户端 | 排除节点 [#排除节点] 你可以通过 `exclude` 参数排除特定节点: ```go // 排除 gRPC 客户端(仅使用 OAuth2) nodes := bSdkStartup.NewStartupConfig("ssoClient") // 排除 OAuth 配置(使用自定义配置) nodes := bSdkStartup.NewStartupConfig("oAuthConfig", "oAuthRedirectURI") ``` 核心模块职责 [#核心模块职责] | 模块 | 目录 | 职责 | | -------------- | ------------- | --------- | | **Startup** | `startup/` | 依赖注入节点定义 | | **Route** | `route/` | Gin 路由注册 | | **Handler** | `handler/` | HTTP 请求处理 | | **Logic** | `logic/` | 业务逻辑封装 | | **Client** | `client/` | gRPC 客户端 | | **Repository** | `repository/` | 数据仓储层 | | **Models** | `models/` | 数据模型定义 | | **Middleware** | `middleware/` | Gin 中间件 | | **Constant** | `constant/` | 常量定义 | | **Utility** | `utility/` | 工具函数 | 两大子系统 [#两大子系统] OAuth2 子系统 [#oauth2-子系统] 基于 `golang.org/x/oauth2` 扩展,提供: * **授权码流程** - State + PKCE 防护 * **Token 管理** - Redis 缓存、自动刷新 * **业务能力** - Userinfo、Introspection(带缓存) gRPC 子系统 [#grpc-子系统] 基于 `connectrpc.com/connect` 实现,提供: | 服务 | 说明 | 认证要求 | | ------------ | -------------- | ------------------- | | **Public** | 公共服务(发送验证码) | 无 | | **Auth** | 认证服务(注册/登录/改密) | App 凭证 | | **User** | 用户服务(获取用户信息) | App 凭证 + User Token | | **Merchant** | 商户服务(标签/公告) | App 凭证 | 与 bamboo-base-go 的集成 [#与-bamboo-base-go-的集成] SDK 依赖 bamboo-base-go 的以下能力: | 能力 | 用途 | | --------------------- | ------------ | | `xReg.Register` | 依赖注入容器 | | `xCtxUtil.MustGetDB` | 从上下文获取数据库 | | `xCtxUtil.MustGetRDB` | 从上下文获取 Redis | | `xError` | 统一错误处理 | | `xLog` | 结构化日志 | | `xResult` | 统一响应格式 | 确保你的项目已正确配置 bamboo-base-go 框架。 # 环境变量配置 (/docs/beacon-sso/go-sdk/environment) 环境变量配置 [#环境变量配置] Beacon SSO SDK 通过环境变量进行配置,分为 **OAuth2 配置** 和 **gRPC 配置** 两部分。 OAuth2 配置 [#oauth2-配置] 必填环境变量 [#必填环境变量] | 变量名 | 说明 | 示例 | | -------------------------------- | ----------------- | ------------------------------------------ | | `SSO_CLIENT_ID` | OAuth2 客户端 ID | `my-app-client` | | `SSO_CLIENT_SECRET` | OAuth2 客户端 Secret | `secret-key-xxx` | | `SSO_REDIRECT_URI` | OAuth2 回调地址 | `http://localhost:8080/api/oauth/callback` | | `SSO_ENDPOINT_AUTH_URI` | 授权端点 | `https://sso.example.com/oauth/authorize` | | `SSO_ENDPOINT_TOKEN_URI` | 令牌端点 | `https://sso.example.com/oauth/token` | | `SSO_ENDPOINT_USERINFO_URI` | 用户信息端点 | `https://sso.example.com/oauth/userinfo` | | `SSO_ENDPOINT_INTROSPECTION_URI` | 令牌自省端点 | `https://sso.example.com/oauth/introspect` | | `SSO_ENDPOINT_REVOCATION_URI` | 令牌注销端点 | `https://sso.example.com/oauth/revoke` | 可选环境变量 [#可选环境变量] | 变量名 | 说明 | 默认值 | | -------------------- | ------ | ----------- | | `SSO_WELL_KNOWN_URI` | 自动发现端点 | 空(需手动配置各端点) | | `SSO_BUSINESS_CACHE` | 业务缓存开关 | `false` | gRPC 配置 [#grpc-配置] 必填环境变量 [#必填环境变量-1] | 变量名 | 说明 | 示例 | | --------------- | --------- | ----------------- | | `SSO_GRPC_HOST` | gRPC 主机地址 | `sso.example.com` | | `SSO_GRPC_PORT` | gRPC 端口 | `5566` | Well-Known 自动发现 [#well-known-自动发现] 如果你配置了 `SSO_WELL_KNOWN_URI`,SDK 会自动从该端点获取以下配置: * `authorization_endpoint` * `token_endpoint` * `userinfo_endpoint` * `introspection_endpoint` * `revocation_endpoint` 配置 `SSO_WELL_KNOWN_URI` 后,可以省略所有 `SSO_ENDPOINT_*` 环境变量。 配置示例 [#配置示例] .env 文件 [#env-文件] ```bash # OAuth2 配置 SSO_CLIENT_ID=my-app-client SSO_CLIENT_SECRET=secret-key-xxx SSO_REDIRECT_URI=http://localhost:8080/api/oauth/callback SSO_ENDPOINT_AUTH_URI=https://sso.example.com/oauth/authorize SSO_ENDPOINT_TOKEN_URI=https://sso.example.com/oauth/token SSO_ENDPOINT_USERINFO_URI=https://sso.example.com/oauth/userinfo SSO_ENDPOINT_INTROSPECTION_URI=https://sso.example.com/oauth/introspect SSO_ENDPOINT_REVOCATION_URI=https://sso.example.com/oauth/revoke # gRPC 配置 SSO_GRPC_HOST=sso.example.com SSO_GRPC_PORT=5566 # 可选配置 SSO_BUSINESS_CACHE=true ``` 使用 Well-Known 自动发现 [#使用-well-known-自动发现] ```bash # 使用自动发现时,只需配置以下变量 SSO_CLIENT_ID=my-app-client SSO_CLIENT_SECRET=secret-key-xxx SSO_REDIRECT_URI=http://localhost:8080/api/oauth/callback SSO_WELL_KNOWN_URI=https://sso.example.com/.well-known/openid-configuration # gRPC 配置仍需手动填写 SSO_GRPC_HOST=sso.example.com SSO_GRPC_PORT=5566 ``` 业务缓存说明 [#业务缓存说明] `SSO_BUSINESS_CACHE` 控制以下业务逻辑的 Redis 缓存: | 功能 | 缓存键模板 | TTL | | ------------- | --------------------------------------------- | ------------------------- | | Userinfo | `oauth:biz:userinfo:{accessToken}` | 30 秒 | | Introspection | `oauth:biz:introspection:{tokenType}:{token}` | 动态(取 min(expiresIn, 30s)) | 开启业务缓存后,用户信息变更可能会有最多 30 秒的延迟。 # Go SDK (/docs/beacon-sso/go-sdk) Go SDK [#go-sdk] 面向 Gin 项目的轻量级 OAuth2/SSO SDK,提供统一登录、用户管理和商户服务能力。 核心特性 [#核心特性] * **OAuth2 授权码流程** - 完整的 State + PKCE 防护机制 * **Token 缓存管理** - 基于 Redis 的 Token 自动缓存与刷新 * **gRPC 服务客户端** - 四大服务(Public / Auth / User / Merchant) * **认证中间件** - 开箱即用的 Token 验证中间件 * **灵活的依赖注入** - 与 bamboo-base-go 框架无缝集成 子系统概览 [#子系统概览] | 子系统 | 说明 | 协议 | | ------------------ | ---------- | ----------------- | | [OAuth2](./oauth2) | 登录/回调/登出流程 | HTTP REST + Redis | | [gRPC](./grpc) | 用户/认证/商户服务 | Connect-RPC | 版本信息 [#版本信息] * 当前版本: v1.0.0 * Go 版本: 1.25.3+ * 依赖框架: bamboo-base-go 快速开始 [#快速开始] 5 分钟快速集成,请参考 [快速开始](./quick-start)。 # 快速开始 (/docs/beacon-sso/go-sdk/quick-start) 快速开始 [#快速开始] 本指南将帮助你在 5 分钟内完成 Beacon SSO SDK 的集成。 安装 [#安装] ```bash go get github.com/phalanx-labs/beacon-sso-sdk ``` 完整示例 [#完整示例] 以下是一个完整的 Gin 服务示例,集成了 OAuth2 登录和 gRPC 服务: ```go package main import ( "context" xConsts "github.com/bamboo-services/bamboo-base-go/defined/context" xReg "github.com/bamboo-services/bamboo-base-go/major/register" xRegNode "github.com/bamboo-services/bamboo-base-go/major/register/node" xResult "github.com/bamboo-services/bamboo-base-go/major/result" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" bSdkRoute "github.com/phalanx-labs/beacon-sso-sdk/route" bSdkStartup "github.com/phalanx-labs/beacon-sso-sdk/startup" "gorm.io/gorm" ) func main() { // 1) 初始化注册节点(数据库、Redis + SDK 配置) nodes := []xRegNode.RegNodeList{ {Key: xConsts.DatabaseKey, Node: initDatabase}, {Key: xConsts.RedisClientKey, Node: initRedis}, } nodes = append(nodes, bSdkStartup.NewStartupConfig()...) reg := xReg.Register(context.Background(), nodes) // 2) 注册业务路由 reg.Serve.GET("/api/status", func(c *gin.Context) { xResult.SuccessHasData(c, "服务正常", gin.H{ "status": "running", }) }) // 3) 挂载 SDK 路由 sdkRoute := bSdkRoute.NewRoute(reg.Init.Ctx) // OAuth2 路由(登录/回调/登出) sdkRoute.OAuthRouter(reg.Serve.Group("/api")) // 账户路由(注册/密码登录/刷新令牌) sdkRoute.AccountRouter(reg.Serve.Group("/api")) // 用户路由(获取用户信息) sdkRoute.UserRouter(reg.Serve.Group("/api")) // 4) 启动服务 _ = reg.Serve.Run(":8080") } // 数据库初始化节点 func initDatabase(ctx context.Context) (any, error) { // TODO: 替换为你的数据库初始化逻辑 var db *gorm.DB return db, nil } // Redis 初始化节点 func initRedis(ctx context.Context) (any, error) { // TODO: 替换为你的 Redis 初始化逻辑 var rdb *redis.Client return rdb, nil } ``` 默认路由 [#默认路由] SDK 会自动注册以下路由: OAuth2 路由 [#oauth2-路由] | 方法 | 路径 | 说明 | | ---- | --------------------- | ------------------------ | | GET | `/api/oauth/login` | 302 重定向到 SSO 授权页面 | | GET | `/api/oauth/callback` | OAuth2 回调,用 code 换 token | | POST | `/api/oauth/logout` | 注销 token | 账户路由 [#账户路由] | 方法 | 路径 | 说明 | | ---- | ------------------------------ | ---- | | POST | `/api/account/register/email` | 邮箱注册 | | POST | `/api/account/login/password` | 密码登录 | | POST | `/api/account/token/refresh` | 刷新令牌 | | POST | `/api/account/password/change` | 修改密码 | | POST | `/api/account/token/revoke` | 注销令牌 | 用户路由 [#用户路由] | 方法 | 路径 | 说明 | | --- | -------------------- | ------------ | | GET | `/api/user/userinfo` | 获取当前用户信息 | | GET | `/api/user/by-id` | 根据 ID 获取用户信息 | 环境变量配置 [#环境变量配置] 在运行前,请确保配置以下环境变量: ```bash # 必填 - OAuth2 配置 SSO_CLIENT_ID=your-client-id SSO_CLIENT_SECRET=your-client-secret SSO_REDIRECT_URI=http://localhost:8080/api/oauth/callback SSO_ENDPOINT_AUTH_URI=https://sso.example.com/oauth/authorize SSO_ENDPOINT_TOKEN_URI=https://sso.example.com/oauth/token SSO_ENDPOINT_USERINFO_URI=https://sso.example.com/oauth/userinfo SSO_ENDPOINT_INTROSPECTION_URI=https://sso.example.com/oauth/introspect SSO_ENDPOINT_REVOCATION_URI=https://sso.example.com/oauth/revoke # 必填 - gRPC 配置 SSO_GRPC_HOST=sso.example.com SSO_GRPC_PORT=5566 ``` 完整的环境变量说明请参考 [环境变量配置](./environment)。 验证安装 [#验证安装] 启动服务后,访问以下地址测试: 1. **登录跳转**: `GET http://localhost:8080/api/oauth/login` 2. **服务状态**: `GET http://localhost:8080/api/status` 下一步 [#下一步] * [环境变量配置](./environment) - 了解所有可配置项 * [架构设计](./architecture) - 理解 SDK 的整体架构 * [OAuth2 子系统](./oauth2) - 深入了解授权流程 * [gRPC 服务](./grpc) - 使用 gRPC 客户端 # 架构设计 (/docs/beacon-sso/java-sdk/architecture) 架构设计 [#架构设计] Beacon SSO Java SDK 采用多模块分层架构设计,基于 Spring Boot 自动配置机制实现零侵入集成。 整体架构 [#整体架构] 多模块结构 [#多模块结构] SDK 采用 Maven 多模块设计: | 模块 | Artifact | 说明 | | ----------------------------- | --------------------------- | ------------------------------------------------- | | **bamboo-sso-base** | `bamboo-sso-base` | 核心模块:属性配置、gRPC/HTTP 客户端、数据模型、缓存、异常 | | **bamboo-sso-sdk-springboot** | `bamboo-sso-sdk-springboot` | Spring Boot Starter:自动配置、Filter、Controller、AOP 切面 | Spring Boot 自动配置 [#spring-boot-自动配置] SDK 通过 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 注册自动配置类: | 配置类 | 条件 | 注册的 Bean | | -------------------------------------- | ------------------------------ | ---------------------------------- | | `AutoConfiguration` | `beacon.sso.enabled=true`(默认) | WebClient、SsoApi、CacheManager | | `BeaconSsoSpringBootAutoConfiguration` | Servlet Web 应用 | BeaconSsoFilter、Controllers、AOP 切面 | | `GrpcConfiguration` | `beacon.sso.grpc.enabled=true` | ManagedChannel | 你只需引入 `bamboo-sso-sdk-springboot` 依赖并在 `application.yml` 中配置属性,所有 Bean 会自动注册到 Spring 容器中。 SsoApi 统一入口 [#ssoapi-统一入口] `SsoApi` 是 SDK 的核心 Facade,通过 Spring IoC 容器注入使用: ```java @Autowired private SsoApi ssoApi; // 账户服务(gRPC) ssoApi.account().registerByEmail(request); ssoApi.account().passwordLogin(request); // 用户服务(gRPC 优先,HTTP 回退) ssoApi.user().getCurrentUser(accessToken, request); // 商户服务(gRPC) ssoApi.merchant().getMerchantTags(); // 公共服务(gRPC) ssoApi.pub().sendRegisterEmailCode(request); ``` | 子 API | 说明 | 传输方式 | | ------------------- | -------------- | --------------- | | `ssoApi.account()` | 账户管理(注册/登录/改密) | gRPC | | `ssoApi.user()` | 用户信息查询 | gRPC 优先,HTTP 回退 | | `ssoApi.merchant()` | 商户标签/公告 | gRPC | | `ssoApi.pub()` | 公共服务(验证码) | gRPC | 双传输架构 [#双传输架构] SDK 支持 HTTP 和 gRPC 两种传输方式: | 功能 | HTTP | gRPC | 说明 | | ----------- | ---- | ------ | ------------ | | OAuth2 登录流程 | 默认 | - | 始终使用 HTTP | | Token 自省 | 默认 | - | 始终使用 HTTP | | 获取当前用户 | 回退 | 优先 | gRPC 启用时优先使用 | | 按 ID 获取用户 | - | 仅 gRPC | 需启用 gRPC | | 邮箱注册/密码登录 | - | 仅 gRPC | 需启用 gRPC | | 商户标签/公告 | - | 仅 gRPC | 需启用 gRPC | OAuth2 授权流程 [#oauth2-授权流程] # 配置参考 (/docs/beacon-sso/java-sdk/configuration) 配置参考 [#配置参考] Beacon SSO Java SDK 通过 `application.yml` / `application.properties` 中的 `beacon.sso.*` 属性进行配置。 核心配置 [#核心配置] 必填配置 [#必填配置] | 属性 | 说明 | 示例 | | -------------------------- | ----------------- | -------------------------------------- | | `beacon.sso.base-url` | SSO 服务器基础 URL | `https://sso.example.com` | | `beacon.sso.client-id` | OAuth2 客户端 ID | `my-app-client` | | `beacon.sso.client-secret` | OAuth2 客户端 Secret | `secret-key-xxx` | | `beacon.sso.redirect-uri` | OAuth2 回调地址 | `http://localhost:8080/oauth/callback` | 可选配置 [#可选配置] | 属性 | 说明 | 默认值 | | --------------------------- | ------------------------ | ------ | | `beacon.sso.enabled` | SDK 总开关 | `true` | | `beacon.sso.exclude-urls` | Filter 排除路径(Ant 风格匹配) | 空 | | `beacon.sso.well-known-uri` | OIDC Well-Known 自动发现 URI | 空 | OAuth2 端点配置 [#oauth2-端点配置] SDK 内置了默认的端点路径,通常无需手动配置: | 属性 | 默认值 | 说明 | | ---------------------------------------- | ------------------- | ------ | | `beacon.sso.endpoints.auth-uri` | `/oauth/authorize` | 授权端点 | | `beacon.sso.endpoints.token-uri` | `/oauth/token` | 令牌端点 | | `beacon.sso.endpoints.userinfo-uri` | `/oauth/userinfo` | 用户信息端点 | | `beacon.sso.endpoints.introspection-uri` | `/oauth/introspect` | 令牌自省端点 | | `beacon.sso.endpoints.revocation-uri` | `/oauth/revoke` | 令牌注销端点 | 以上端点路径会与 `beacon.sso.base-url` 拼接成完整 URL。例如 `base-url` 为 `https://sso.example.com` 时,Token 端点为 `https://sso.example.com/oauth/token`。 路由前缀配置 [#路由前缀配置] SDK 自动注册的 Controller 路径前缀均可自定义: | 属性 | 默认值 | 说明 | | -------------------------- | ----------- | ----------- | | `beacon.sso.oauth-path` | `/oauth` | OAuth2 路由前缀 | | `beacon.sso.account-path` | `/account` | 账户路由前缀 | | `beacon.sso.user-path` | `/user` | 用户路由前缀 | | `beacon.sso.public-path` | `/public` | 公共路由前缀 | | `beacon.sso.merchant-path` | `/merchant` | 商户路由前缀 | 例如将 OAuth 路由前缀改为 `/sso`: ```yaml beacon: sso: oauth-path: /sso ``` 则登录路由变为 `/sso/login`,回调路由变为 `/sso/callback`。 gRPC 配置 [#grpc-配置] gRPC 默认**关闭**,需要手动启用: 必填配置(启用 gRPC 时) [#必填配置启用-grpc-时] | 属性 | 说明 | 示例 | | -------------------------------- | ---------- | ----------------- | | `beacon.sso.grpc.enabled` | 启用 gRPC | `true` | | `beacon.sso.grpc.host` | gRPC 服务器地址 | `sso.example.com` | | `beacon.sso.grpc.port` | gRPC 服务器端口 | `5566` | | `beacon.sso.grpc.app-access-id` | 应用凭证 ID | `app-xxx` | | `beacon.sso.grpc.app-secret-key` | 应用凭证密钥 | `secret-xxx` | Well-Known 自动发现 [#well-known-自动发现] 如果你配置了 `beacon.sso.well-known-uri`,SDK 会在启动时自动从该端点获取 OAuth2 端点配置: * `authorization_endpoint` * `token_endpoint` * `userinfo_endpoint` * `introspection_endpoint` * `revocation_endpoint` 配置 `well-known-uri` 后,无需手动配置 `beacon.sso.endpoints.*` 相关属性。自动发现**仅覆盖**仍为默认值的端点路径。 完整配置示例 [#完整配置示例] 基础配置(仅 OAuth2) [#基础配置仅-oauth2] ```yaml beacon: sso: base-url: https://sso.example.com client-id: my-app-client client-secret: secret-key-xxx redirect-uri: http://localhost:8080/oauth/callback exclude-urls: - /api/public/** - /actuator/** ``` 完整配置(OAuth2 + gRPC) [#完整配置oauth2--grpc] ```yaml beacon: sso: base-url: https://sso.example.com client-id: my-app-client client-secret: secret-key-xxx redirect-uri: http://localhost:8080/oauth/callback exclude-urls: - /api/public/** grpc: enabled: true host: sso.example.com port: 5566 app-access-id: app-xxx app-secret-key: secret-xxx ``` 使用 Well-Known 自动发现 [#使用-well-known-自动发现] ```yaml beacon: sso: base-url: https://sso.example.com client-id: my-app-client client-secret: secret-key-xxx redirect-uri: http://localhost:8080/oauth/callback well-known-uri: https://sso.example.com/.well-known/openid-configuration grpc: enabled: true host: sso.example.com port: 5566 app-access-id: app-xxx app-secret-key: secret-xxx ``` # Java SDK (/docs/beacon-sso/java-sdk) Java SDK [#java-sdk] 基于 Spring Boot 3 的轻量级 OAuth2/SSO SDK,提供统一登录、用户管理和商户服务能力。 核心特性 [#核心特性] * **OAuth2 授权码流程** - 完整的 State + PKCE (S256) 防护机制 * **Spring Boot 自动配置** - 引入 Starter 即可,零配置启动 * **双传输架构** - HTTP(默认)+ 可选 gRPC 服务 * **Filter + AOP 认证** - `BeaconSsoFilter` 过滤器 + `@InjectData` / `@PermissionVerify` 注解 * **Caffeine 缓存** - 基于 Caffeine 的高性能内存缓存 子系统概览 [#子系统概览] | 子系统 | 说明 | 协议 | | ------------------ | ---------- | -------------------- | | [OAuth2](./oauth2) | 登录/回调/登出流程 | HTTP REST + Caffeine | | [gRPC](./grpc) | 用户/认证/商户服务 | gRPC(可选) | 版本信息 [#版本信息] * 当前版本: 0.0.1-SNAPSHOT * Java 版本: 17+ * Spring Boot: 3.5.11 * Maven 坐标: `com.frontleaves.phalanx.beacon.sso:bamboo-sso-sdk-springboot` 快速开始 [#快速开始] 5 分钟快速集成,请参考 [快速开始](./quick-start)。 # 快速开始 (/docs/beacon-sso/java-sdk/quick-start) 快速开始 [#快速开始] 本指南将帮助你在 5 分钟内完成 Beacon SSO Java SDK 的集成。 添加依赖 [#添加依赖] 在 `pom.xml` 中添加 SDK 依赖: ```xml com.frontleaves.phalanx.beacon.sso bamboo-sso-sdk-springboot 0.0.1-SNAPSHOT ``` 配置 [#配置] 在 `application.yml` 中添加最小配置: ```yaml beacon: sso: base-url: https://sso.example.com client-id: your-client-id client-secret: your-client-secret redirect-uri: http://localhost:8080/oauth/callback ``` SDK 通过 Spring Boot 自动配置机制,会在应用启动时自动注册 Filter、Controller 和 AOP 切面。你无需编写任何额外代码即可使用 OAuth2 登录流程。 启动应用 [#启动应用] 添加依赖和配置后,直接启动你的 Spring Boot 应用即可: ```java @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 使用示例 [#使用示例] 获取当前用户信息 [#获取当前用户信息] SDK 自动注册了 `BeaconSsoFilter`,所有请求(排除 `beacon.sso.exclude-urls` 配置的路径)都会进行 Token 验证。在 Controller 中通过 `@InjectData` 注解获取用户信息: ```java @RestController @RequestMapping("/api") public class DemoController { @GetMapping("/profile") public BaseResponse getProfile( @InjectData("sub") String userId) { // 直接使用注入的 userId return BaseResponse.success("获取成功", null); } } ``` 注入完整 Token 信息 [#注入完整-token-信息] ```java @GetMapping("/token-info") public BaseResponse getTokenInfo( @InjectData IntrospectResult introspectResult) { return BaseResponse.success("获取成功", introspectResult); } ``` 默认路由 [#默认路由] SDK 自动注册以下路由(路径前缀可配置): OAuth2 路由 [#oauth2-路由] | 方法 | 路径 | 说明 | | --- | ----------------- | ------------------------ | | GET | `/oauth/login` | 302 重定向到 SSO 授权页面 | | GET | `/oauth/callback` | OAuth2 回调,用 code 换 token | | GET | `/oauth/logout` | 注销并清除 Session | | GET | `/oauth/status` | 获取当前认证状态 | 账户路由 [#账户路由] | 方法 | 路径 | 说明 | | ---- | -------------------------- | -------- | | POST | `/account/register/email` | 邮箱注册 | | POST | `/account/login/password` | 密码登录 | | POST | `/account/password/change` | 修改密码 | | POST | `/account/logout` | 注销 Token | 用户路由(需 gRPC) [#用户路由需-grpc] | 方法 | 路径 | 说明 | | --- | ---------------- | ------------ | | GET | `/user/userinfo` | 获取当前用户信息 | | GET | `/user/{userId}` | 根据 ID 获取用户详情 | 账户路由和用户路由依赖 gRPC 服务,需额外配置 `beacon.sso.grpc.enabled=true` 及相关连接参数。 验证安装 [#验证安装] 启动服务后,访问以下地址测试: 1. **登录跳转**: `GET http://localhost:8080/oauth/login` 2. **认证状态**: `GET http://localhost:8080/oauth/status` 下一步 [#下一步] * [配置参考](./configuration) - 了解所有可配置项 * [架构设计](./architecture) - 理解 SDK 的整体架构 * [OAuth2 子系统](./oauth2) - 深入了解授权流程 * [gRPC 服务](./grpc) - 启用和使用 gRPC 客户端 # 缓存策略 (/docs/beacon-sso/go-sdk/advanced/cache) 缓存策略 [#缓存策略] SDK 使用 Redis 进行多层次缓存,以提升性能和减少对 SSO Server 的请求。 缓存层级 [#缓存层级] 缓存详情 [#缓存详情] OAuth2 核心缓存 [#oauth2-核心缓存] | 缓存类型 | Key 模板 | TTL | 可控 | | ---------- | --------------------------- | ----- | -- | | State/PKCE | `oauth:state:{state}` | 15 分钟 | 否 | | Token | `oauth:token:{accessToken}` | 30 天 | 否 | **说明:** OAuth2 核心缓存始终启用,无法关闭。 业务缓存 [#业务缓存] | 缓存类型 | Key 模板 | TTL | 可控 | | ------------- | --------------------------------------------- | ---- | -- | | Userinfo | `oauth:biz:userinfo:{accessToken}` | 30 秒 | 是 | | Introspection | `oauth:biz:introspection:{tokenType}:{token}` | 动态 | 是 | **动态 TTL 计算:** Introspection 的 TTL 取 `min(expiresIn, 30s)`,确保不会缓存已过期的结果。 业务缓存开关 [#业务缓存开关] 通过 `SSO_BUSINESS_CACHE` 环境变量控制: ```bash # 开启业务缓存(默认关闭) SSO_BUSINESS_CACHE=true ``` 开启缓存的影响 [#开启缓存的影响] | 影响 | 说明 | | -------- | ------------------------ | | **性能提升** | 减少对 SSO Server 的 HTTP 请求 | | **数据延迟** | 用户信息变更最多延迟 30 秒 | | **内存占用** | Redis 占用增加 | 开启业务缓存后,用户信息变更(如修改昵称)可能会有最多 30 秒的延迟。如果你的应用需要实时的用户信息,请保持缓存关闭。 使用 BusinessLogic [#使用-businesslogic] BusinessLogic 提供 Userinfo 和 Introspection 能力,缓存逻辑已内置: ```go import bSdkLogic "github.com/phalanx-labs/beacon-sso-sdk/logic" func main() { ctx := context.Background() businessLogic := bSdkLogic.NewBusiness(ctx) // 获取用户信息(带缓存) userinfo, err := businessLogic.Userinfo(ctx, accessToken) if err != nil { panic(err) } fmt.Printf("用户: %s\n", userinfo.Nickname) // 查询令牌状态(带缓存) intro, err := businessLogic.Introspection(ctx, "access_token", accessToken) if err != nil { panic(err) } fmt.Printf("令牌状态: %v\n", intro.Active) fmt.Printf("过期时间: %s\n", intro.Expiry) } ``` 手动清理缓存 [#手动清理缓存] 如果需要在用户信息变更后立即清理缓存: ```go // 目前需要直接操作 Redis rdb.Del(ctx, "oauth:biz:userinfo:"+accessToken) rdb.Del(ctx, "oauth:biz:introspection:access_token:"+accessToken) ``` 缓存模型 [#缓存模型] CacheOAuth [#cacheoauth] ```go type CacheOAuth struct { State string // 随机状态码 Verifier string // PKCE 验证器 } ``` CacheOAuthToken [#cacheoauthtoken] ```go type CacheOAuthToken struct { AccessToken string // 访问令牌 TokenType string // 令牌类型 RefreshToken string // 刷新令牌 Expiry string // 过期时间(RFC3339) } ``` OAuthUserinfo [#oauthuserinfo] ```go type OAuthUserinfo struct { Sub string // 用户唯一标识 Nickname string // 昵称 PreferredUsername string // 首选用户名 Email string // 邮箱 Phone string // 手机号 Raw map[string]any // 原始响应数据 } ``` OAuthIntrospection [#oauthintrospection] ```go type OAuthIntrospection struct { Active bool // 令牌是否有效 TokenType string // 令牌类型 Exp int64 // 过期时间戳(Unix) Expiry string // 过期时间(RFC3339) ExpiresIn int64 // 剩余有效时间(秒) IsExpired bool // 是否已过期 Raw map[string]any // 原始响应数据 } ``` # 高级话题 (/docs/beacon-sso/go-sdk/advanced) 高级话题 [#高级话题] 本章节介绍 SDK 的高级配置和定制选项。 子文档 [#子文档] 了解 SDK 的 Redis 缓存机制和配置选项 自定义初始化 [#自定义初始化] 排除特定节点 [#排除特定节点] 你可以通过 `exclude` 参数排除不需要的初始化节点: ```go // 仅使用 OAuth2,排除 gRPC 客户端 nodes := bSdkStartup.NewStartupConfig("ssoClient") // 排除 OAuth 配置(使用自定义 OAuth 配置) nodes := bSdkStartup.NewStartupConfig("oAuthConfig", "oAuthRedirectURI") ``` 可排除的节点 [#可排除的节点] | 节点名称 | 说明 | | ------------------ | ------------------ | | `oAuthConfig` | OAuth2 核心配置 | | `oAuthRedirectURI` | OAuth2 重定向地址 | | `ssoClient` | SsoClient gRPC 客户端 | 独立使用 Logic 层 [#独立使用-logic-层] 你可以直接实例化 Logic 层,而不使用 SDK 提供的路由: ```go import bSdkLogic "github.com/phalanx-labs/beacon-sso-sdk/logic" func customHandler(c *gin.Context) { ctx := c.Request.Context() // 使用 OAuthLogic oauthLogic := bSdkLogic.NewOAuth(ctx) // 使用 AuthLogic authLogic := bSdkLogic.NewAuth(ctx) // 使用 UserLogic userLogic := bSdkLogic.NewUser(ctx) // 使用 BusinessLogic businessLogic := bSdkLogic.NewBusiness(ctx) } ``` 直接使用 gRPC 客户端 [#直接使用-grpc-客户端] 如果不需要 OAuth2 功能,可以只使用 gRPC 客户端: ```go import bSdkClient "github.com/phalanx-labs/beacon-sso-sdk/client" func main() { // 仅创建 gRPC 客户端 client := bSdkClient.NewClient( bSdkClient.WithConnect("sso.example.com", "5566"), bSdkClient.WithAppAccess("app-id", "app-secret"), ) // 使用 gRPC 服务 client.Auth.PasswordLogin(ctx, req) client.User.GetCurrentUser(ctx, token) client.Merchant.GetMerchantTags(ctx, req) } ``` # 错误码 (/docs/beacon-sso/go-sdk/api/errors) 错误码 [#错误码] SDK 使用 bamboo-base-go 的统一错误处理体系。 错误结构 [#错误结构] 所有错误都遵循统一的结构: ```go type Error struct { ErrorCode ErrorCode // 错误码 ErrorMessage string // 错误消息 Data any // 附加数据 } ``` 常见错误码 [#常见错误码] 参数错误 [#参数错误] | 错误码 | 说明 | HTTP 状态码 | | ---------------- | ------ | -------- | | `ParameterEmpty` | 必填参数为空 | 400 | | `ParameterError` | 参数格式错误 | 400 | 认证错误 [#认证错误] | 错误码 | 说明 | HTTP 状态码 | | -------------- | ------------- | -------- | | `Unauthorized` | 未登录或 Token 无效 | 401 | | `TokenExpired` | Token 已过期 | 401 | 资源错误 [#资源错误] | 错误码 | 说明 | HTTP 状态码 | | ---------- | ----- | -------- | | `NotExist` | 资源不存在 | 404 | 操作错误 [#操作错误] | 错误码 | 说明 | HTTP 状态码 | | ----------------- | ---- | -------- | | `OperationFailed` | 操作失败 | 500 | 错误响应示例 [#错误响应示例] 参数为空 [#参数为空] ```json { "code": 400, "message": "状态为空", "data": null } ``` Token 过期 [#token-过期] ```json { "code": 401, "message": "访问令牌已过期", "data": null } ``` 未登录 [#未登录] ```json { "code": 401, "message": "未登录", "data": null } ``` 错误处理 [#错误处理] 在 Handler 中 [#在-handler-中] ```go import xResult "github.com/bamboo-services/bamboo-base-go/major/result" func handler(c *gin.Context) { result, err := someLogic.DoSomething(ctx) if err != nil { xResult.AbortError(c, err.ErrorCode, err.ErrorMessage, err.Data) return } xResult.SuccessHasData(c, "操作成功", result) } ``` 在 Logic 层 [#在-logic-层] ```go import xError "github.com/bamboo-services/bamboo-base-go/common/error" func (l *SomeLogic) DoSomething(ctx context.Context) (*Result, *xError.Error) { if someCondition { return nil, xError.NewError(ctx, xError.ParameterEmpty, "参数为空", false, nil) } if someError { return nil, xError.NewError(ctx, xError.OperationFailed, "操作失败", false, someErr) } return &Result{}, nil } ``` OAuth2 特定错误 [#oauth2-特定错误] State 验证失败 [#state-验证失败] ```json { "code": 401, "message": "验证器不存在", "data": null } ``` **原因:** * State 不存在或已过期 * State 已被使用(重放攻击) Code 换取 Token 失败 [#code-换取-token-失败] ```json { "code": 401, "message": "未登录", "data": null } ``` **原因:** * 授权码无效或已过期 * PKCE Verifier 不匹配 * 客户端凭证错误 gRPC 错误 [#grpc-错误] Connect-RPC 使用标准的 gRPC 错误码: | 错误码 | 说明 | | -------------------- | ----- | | `canceled` | 操作被取消 | | `unknown` | 未知错误 | | `invalid_argument` | 参数无效 | | `deadline_exceeded` | 超时 | | `not_found` | 资源不存在 | | `already_exists` | 资源已存在 | | `permission_denied` | 权限不足 | | `unauthenticated` | 未认证 | | `resource_exhausted` | 资源耗尽 | | `unavailable` | 服务不可用 | 处理 gRPC 错误 [#处理-grpc-错误] ```go import "connectrpc.com/connect" resp, err := client.Auth.PasswordLogin(ctx, req) if err != nil { if connectErr, ok := err.(*connect.Error); ok { switch connectErr.Code() { case connect.CodeUnauthenticated: // 认证失败 case connect.CodeInvalidArgument: // 参数无效 case connect.CodeUnavailable: // 服务不可用 } } return } ``` 错误处理最佳实践 [#错误处理最佳实践] 1. **始终检查错误** - 不要忽略任何错误返回值 2. **使用适当的日志级别** - 错误用 Error,警告用 Warn 3. **提供有意义的错误信息** - 便于调试和用户理解 4. **不要暴露敏感信息** - 如 Token、密码等 5. **区分客户端和服务端错误** - 4xx vs 5xx # API 参考 (/docs/beacon-sso/go-sdk/api) API 参考 [#api-参考] 本章节提供 SDK 的完整 API 参考。 子文档 [#子文档] 所有数据模型的字段说明 常见错误码及处理方式 核心包 [#核心包] | 包路径 | 说明 | | --------------------------------------------------- | ---------- | | `github.com/phalanx-labs/beacon-sso-sdk/startup` | 依赖注入节点 | | `github.com/phalanx-labs/beacon-sso-sdk/route` | Gin 路由注册 | | `github.com/phalanx-labs/beacon-sso-sdk/handler` | HTTP 请求处理器 | | `github.com/phalanx-labs/beacon-sso-sdk/logic` | 业务逻辑层 | | `github.com/phalanx-labs/beacon-sso-sdk/client` | gRPC 客户端 | | `github.com/phalanx-labs/beacon-sso-sdk/middleware` | Gin 中间件 | | `github.com/phalanx-labs/beacon-sso-sdk/models` | 数据模型 | | `github.com/phalanx-labs/beacon-sso-sdk/repository` | 数据仓储 | | `github.com/phalanx-labs/beacon-sso-sdk/constant` | 常量定义 | | `github.com/phalanx-labs/beacon-sso-sdk/utility` | 工具函数 | # 数据模型 (/docs/beacon-sso/go-sdk/api/models) 数据模型 [#数据模型] 本页面列出 SDK 中所有核心数据模型。 OAuth2 模型 [#oauth2-模型] CacheOAuth [#cacheoauth] OAuth2 State 和 PKCE Verifier 的缓存模型。 ```go type CacheOAuth struct { State string // 随机状态码(32 位大写字母) Verifier string // PKCE Code Verifier } ``` | 字段 | 类型 | 说明 | | ---------- | ------ | ---------------------- | | `State` | string | 用于验证请求完整性的随机状态码 | | `Verifier` | string | PKCE 流程的 Code Verifier | **Redis Key:** `oauth:state:{state}` **TTL:** 15 分钟 *** CacheOAuthToken [#cacheoauthtoken] OAuth2 Token 的缓存模型。 ```go type CacheOAuthToken struct { AccessToken string // 访问令牌 TokenType string // 令牌类型(Bearer) RefreshToken string // 刷新令牌 Expiry string // 过期时间(RFC3339 格式) } ``` | 字段 | 类型 | 说明 | | -------------- | ------ | ----------------- | | `AccessToken` | string | 访问令牌 | | `TokenType` | string | 令牌类型,通常为 "Bearer" | | `RefreshToken` | string | 用于刷新 Access Token | | `Expiry` | string | 过期时间,RFC3339 格式 | **Redis Key:** `oauth:token:{accessToken}` **TTL:** 30 天 *** OAuthUserinfo [#oauthuserinfo] OAuth2 Userinfo Endpoint 的响应模型。 ```go type OAuthUserinfo struct { Sub string // 用户唯一标识 Nickname string // 昵称 PreferredUsername string // 首选用户名 Email string // 邮箱 Phone string // 手机号 Raw map[string]any // 原始响应数据 } ``` | 字段 | 类型 | 说明 | | ------------------- | --------------- | ----------------- | | `Sub` | string | 用户唯一标识(来自 OIDC) | | `Nickname` | string | 用户昵称 | | `PreferredUsername` | string | 用户首选用户名 | | `Email` | string | 用户邮箱 | | `Phone` | string | 用户手机号 | | `Raw` | map\[string]any | 原始 JSON 响应,包含扩展字段 | **Redis Key:** `oauth:biz:userinfo:{accessToken}` **TTL:** 30 秒(受 `SSO_BUSINESS_CACHE` 控制) *** OAuthIntrospection [#oauthintrospection] OAuth2 Introspection Endpoint 的响应模型。 ```go type OAuthIntrospection struct { Active bool // 令牌是否有效 TokenType string // 令牌类型 Exp int64 // 过期时间戳(Unix) Expiry string // 过期时间(RFC3339) ExpiresIn int64 // 剩余有效时间(秒) IsExpired bool // 是否已过期 Raw map[string]any // 原始响应数据 } ``` | 字段 | 类型 | 说明 | | ----------- | --------------- | ---------------- | | `Active` | bool | 令牌是否有效 | | `TokenType` | string | 令牌类型 | | `Exp` | int64 | 过期时间戳(Unix 秒) | | `Expiry` | string | 过期时间(RFC3339 格式) | | `ExpiresIn` | int64 | 剩余有效时间(秒) | | `IsExpired` | bool | 是否已过期 | | `Raw` | map\[string]any | 原始 JSON 响应 | **Redis Key:** `oauth:biz:introspection:{tokenType}:{token}` **TTL:** 动态(取 min(expiresIn, 30s)) *** 业务缓存模型 [#业务缓存模型] CacheBusinessUserinfo [#cachebusinessuserinfo] 业务层 Userinfo 的 Redis Hash 存储模型。 ```go type CacheBusinessUserinfo struct { Sub string // 用户唯一标识 Raw string // 原始 JSON 字符串 } ``` *** CacheBusinessIntrospection [#cachebusinessintrospection] 业务层 Introspection 的 Redis Hash 存储模型。 ```go type CacheBusinessIntrospection struct { Active bool // 令牌是否有效 TokenType string // 令牌类型 Exp int64 // 过期时间戳 ExpiresIn int64 // 剩余有效时间 IsExpired bool // 是否已过期 Raw string // 原始 JSON 字符串 } ``` *** gRPC 消息类型 [#grpc-消息类型] SendRegisterEmailCodeRequest [#sendregisteremailcoderequest] ```go type SendRegisterEmailCodeRequest struct { Email string // 目标邮箱地址 } ``` RegisterByEmailRequest [#registerbyemailrequest] ```go type RegisterByEmailRequest struct { Email string // 邮箱地址 Code string // 验证码 Username string // 用户名 Password string // 密码 Nickname string // 昵称(可选) } ``` RegisterByEmailResponse [#registerbyemailresponse] ```go type RegisterByEmailResponse struct { UserId string // 用户 ID AccessToken string // 访问令牌 RefreshToken string // 刷新令牌 ExpiresIn int64 // 过期时间(秒) } ``` PasswordLoginRequest [#passwordloginrequest] ```go type PasswordLoginRequest struct { Username string // 用户名/邮箱/手机号 Password string // 密码 Scope string // 权限范围 } ``` PasswordLoginResponse [#passwordloginresponse] ```go type PasswordLoginResponse struct { AccessToken string // 访问令牌 TokenType string // 令牌类型 RefreshToken string // 刷新令牌 ExpiresIn int64 // 过期时间(秒) IdToken string // ID Token(可选) } ``` ChangePasswordRequest [#changepasswordrequest] ```go type ChangePasswordRequest struct { UserId string // 用户 ID OldPassword string // 旧密码(普通模式必填) NewPassword string // 新密码 NeedResetPassword bool // 是否强制重置模式 } ``` RevokeTokenRequest [#revoketokenrequest] ```go type RevokeTokenRequest struct { TokenTypeHint string // 令牌类型提示(可选) } ``` GetUserByIDRequest [#getuserbyidrequest] ```go type GetUserByIDRequest struct { UserId string // 用户 ID } ``` # Auth 服务 (/docs/beacon-sso/go-sdk/grpc/auth-service) Auth 服务 [#auth-服务] Auth 服务提供用户认证相关功能,包括注册、登录、修改密码和注销令牌。 Auth 服务需要在 metadata 中提供有效的 App 凭证(`app-access-id` 和 `app-secret-key`)。 接口概览 [#接口概览] | 方法 | 说明 | 返回 Token | | ----------------- | ---- | -------- | | `RegisterByEmail` | 邮箱注册 | ✓ | | `PasswordLogin` | 密码登录 | ✓ | | `ChangePassword` | 修改密码 | ✗ | | `RevokeToken` | 注销令牌 | ✗ | RegisterByEmail [#registerbyemail] 通过邮箱验证码完成用户注册,注册成功后自动生成登录 Token。 请求参数 [#请求参数] ```go type RegisterByEmailRequest struct { Email string // 邮箱地址 Code string // 验证码 Username string // 用户名 Password string // 密码 Nickname string // 昵称(可选) } ``` 响应 [#响应] ```go type RegisterByEmailResponse struct { UserId string // 用户 ID AccessToken string // 访问令牌 RefreshToken string // 刷新令牌 ExpiresIn int64 // 过期时间(秒) } ``` 使用示例 [#使用示例] ```go // 1. 先发送验证码 _, err := client.Public.SendRegisterEmailCode(ctx, &pb.SendRegisterEmailCodeRequest{ Email: "user@example.com", }) // 2. 用户输入验证码后注册 resp, err := client.Auth.RegisterByEmail(ctx, &pb.RegisterByEmailRequest{ Email: "user@example.com", Code: "123456", Username: "newuser", Password: "SecurePass123!", Nickname: "New User", }) fmt.Printf("注册成功!用户 ID: %s\n", resp.UserId) fmt.Printf("Access Token: %s\n", resp.AccessToken) ``` PasswordLogin [#passwordlogin] 实现 OAuth 2.0 Resource Owner Password Credentials Grant,允许受信任的第一方客户端直接使用用户名和密码换取 Token。 安全限制 [#安全限制] * **仅限第一方应用**(`App.FirstParty = enabled`) * 支持用户名/邮箱/手机号三种登录方式(自动识别) 请求参数 [#请求参数-1] ```go type PasswordLoginRequest struct { Username string // 用户名/邮箱/手机号 Password string // 密码 Scope string // 权限范围(如 "openid profile email") } ``` 响应 [#响应-1] ```go type PasswordLoginResponse struct { AccessToken string // 访问令牌 TokenType string // 令牌类型(通常为 "Bearer") RefreshToken string // 刷新令牌 ExpiresIn int64 // 过期时间(秒) IdToken string // ID Token(可选) } ``` 使用示例 [#使用示例-1] ```go resp, err := client.Auth.PasswordLogin(ctx, &pb.PasswordLoginRequest{ Username: "user@example.com", Password: "SecurePass123!", Scope: "openid profile email", }) if err != nil { panic(err) } fmt.Printf("登录成功!\n") fmt.Printf("Access Token: %s\n", resp.AccessToken) fmt.Printf("过期时间: %d 秒\n", resp.ExpiresIn) ``` 密码登录会自动缓存 Token 到 Redis,可通过 `OAuthLogic.GetToken()` 获取。 ChangePassword [#changepassword] 修改用户密码,支持普通模式和强制重置模式。 模式说明 [#模式说明] | 模式 | 条件 | 旧密码 | | ------ | ------------------------- | --- | | 普通模式 | `NeedResetPassword=false` | 必填 | | 强制重置模式 | `NeedResetPassword=true` | 可省略 | 请求参数 [#请求参数-2] ```go type ChangePasswordRequest struct { UserId string // 用户 ID OldPassword string // 旧密码(普通模式必填) NewPassword string // 新密码 NeedResetPassword bool // 是否强制重置模式 } ``` 响应 [#响应-2] ```go type ChangePasswordResponse struct { // 基础响应信息 } ``` 使用示例 [#使用示例-2] ```go // 普通模式修改密码 _, err := client.Auth.ChangePassword(ctx, &pb.ChangePasswordRequest{ UserId: "user-123", OldPassword: "OldPass123!", NewPassword: "NewSecurePass456!", }) // 强制重置模式(管理员场景) _, err := client.Auth.ChangePassword(ctx, &pb.ChangePasswordRequest{ UserId: "user-123", NewPassword: "NewSecurePass456!", NeedResetPassword: true, }) ``` RevokeToken [#revoketoken] 注销用户 Token,实现用户登出功能。符合 RFC 7009 OAuth 2.0 Token Revocation 规范。 使用场景 [#使用场景] * 用户主动登出 * Token 泄露后的紧急注销 请求参数 [#请求参数-3] ```go type RevokeTokenRequest struct { TokenTypeHint string // 令牌类型提示(access_token/refresh_token,可选) } // 方法签名 func (s *AuthService) RevokeToken( ctx context.Context, accessToken string, // 用户访问令牌 req *pb.RevokeTokenRequest, ) (*pb.RevokeTokenResponse, error) ``` 使用示例 [#使用示例-3] ```go // 注销 Access Token _, err := client.Auth.RevokeToken(ctx, accessToken, &pb.RevokeTokenRequest{ TokenTypeHint: "access_token", }) // 注销 Refresh Token _, err := client.Auth.RevokeToken(ctx, refreshToken, &pb.RevokeTokenRequest{ TokenTypeHint: "refresh_token", }) ``` SDK 的 HTTP 路由层调用 `RevokeToken` 后会自动清理本地 Redis 缓存。 HTTP 路由 [#http-路由] SDK 提供以下 Auth 相关 HTTP 路由: | 方法 | 路径 | 说明 | | ---- | ------------------------------ | --------- | | POST | `/api/account/register/email` | 邮箱注册 | | POST | `/api/account/login/password` | 密码登录 | | POST | `/api/account/password/change` | 修改密码(需认证) | | POST | `/api/account/token/revoke` | 注销令牌(需认证) | # gRPC 服务 (/docs/beacon-sso/go-sdk/grpc) gRPC 服务 [#grpc-服务] gRPC 服务基于 Connect-RPC 协议实现,提供认证、用户和商户三大服务。 协议说明 [#协议说明] SDK 使用 [Connect-RPC](https://connectrpc.com/) 协议,这是一种基于 HTTP/2 的 gRPC 兼容协议,具有以下特点: * 完全兼容 gRPC * 支持 HTTP/2 明文传输(h2c) * 自动生成类型安全的客户端代码 SsoClient 初始化 [#ssoclient-初始化] Option 模式 [#option-模式] SDK 使用 Option 模式初始化客户端: ```go import bSdkClient "github.com/phalanx-labs/beacon-sso-sdk/client" client := bSdkClient.NewClient( bSdkClient.WithConnect("sso.example.com", "5566"), bSdkClient.WithAppAccess("your-app-id", "your-app-secret"), ) ``` 可用 Option [#可用-option] | Option | 说明 | | --------------------------------- | ---------------------- | | `WithConnect(host, port)` | 设置 gRPC 主机地址和端口 | | `WithAppAccess(id, secret)` | 设置 App 认证凭证 | | `WithProtoPublicClient(client)` | 直接注入 Public 客户端(测试用) | | `WithProtoAuthClient(client)` | 直接注入 Auth 客户端(测试用) | | `WithProtoMerchantClient(client)` | 直接注入 Merchant 客户端(测试用) | | `WithProtoUserClient(client)` | 直接注入 User 客户端(测试用) | 通过依赖注入获取 [#通过依赖注入获取] 如果你的项目已集成 SDK 的 Startup 节点,可以从上下文中获取: ```go import bSdkUtil "github.com/phalanx-labs/beacon-sso-sdk/utility" client := bSdkUtil.GetSsoClient(ctx) ``` 服务概览 [#服务概览] | 服务 | 说明 | 认证要求 | | ------------------------------ | ---- | ------------------- | | [Public](./public-service) | 公共服务 | 无 | | [Auth](./auth-service) | 认证服务 | App 凭证 | | [User](./user-service) | 用户服务 | App 凭证 + User Token | | [Merchant](./merchant-service) | 商户服务 | App 凭证 | 认证机制 [#认证机制] App 凭证 [#app-凭证] Auth、User、Merchant 服务需要在 gRPC metadata 中提供 App 凭证: ```go // metadata 中会自动注入 // app-access-id: your-app-id // app-secret-key: your-app-secret ``` User Token [#user-token] User 服务还需要在 metadata 中提供用户 Token: ```go // metadata 中会自动注入 // authorization: Bearer {accessToken} ``` 服务封装层会自动处理 metadata 注入,你只需在调用时传入 accessToken 参数。 使用示例 [#使用示例] ```go import ( "context" bSdkClient "github.com/phalanx-labs/beacon-sso-sdk/client" pb "github.com/phalanx-labs/beacon-sso-sdk/client/api/beacon/sso/v1" ) func main() { ctx := context.Background() // 创建客户端 client := bSdkClient.NewClient( bSdkClient.WithConnect("sso.example.com", "5566"), bSdkClient.WithAppAccess("app-id", "app-secret"), ) // 使用 Public 服务 _, err := client.Public.SendRegisterEmailCode(ctx, &pb.SendRegisterEmailCodeRequest{ Email: "user@example.com", }) // 使用 Auth 服务 loginResp, err := client.Auth.PasswordLogin(ctx, &pb.PasswordLoginRequest{ Username: "user@example.com", Password: "password123", Scope: "openid profile email", }) // 使用 User 服务(需要 accessToken) userResp, err := client.User.GetCurrentUser(ctx, loginResp.AccessToken) // 使用 Merchant 服务 tagsResp, err := client.Merchant.GetMerchantTags(ctx, &service.GetMerchantTagsRequest{}) } ``` 子文档 [#子文档] 发送注册验证码等公共功能 注册、登录、修改密码、注销令牌 获取用户信息 商户标签、公告管理 # Merchant 服务 (/docs/beacon-sso/go-sdk/grpc/merchant-service) Merchant 服务 [#merchant-服务] Merchant 服务提供商户标签和公告管理功能。 Merchant 服务需要在 metadata 中提供 App 凭证(`app-access-id` 和 `app-secret-key`)。 接口概览 [#接口概览] | 方法 | 说明 | | ------------------------ | ----------- | | `GetMerchantTags` | 获取商户标签列表 | | `GetUserTags` | 获取用户标签列表 | | `CheckUserHasTag` | 检查用户是否有指定标签 | | `GetRecentAnnouncements` | 获取最近公告列表 | | `GetAnnouncement` | 获取单个公告详情 | 标签系统 [#标签系统] GetMerchantTags [#getmerchanttags] 获取当前应用所属商户的所有标签列表。 ```go import service "github.com/phalanx-labs/beacon-sso-sdk/client/service" tags, err := client.Merchant.GetMerchantTags(ctx, &service.GetMerchantTagsRequest{}) if err != nil { panic(err) } for _, tag := range tags.Tags { fmt.Printf("标签: %s (%s)\n", tag.Name, tag.Code) } ``` GetUserTags [#getusertags] 获取指定用户在当前商户的所有标签。 ```go tags, err := client.Merchant.GetUserTags(ctx, &service.GetUserTagsRequest{ UserId: "user-123", }) for _, tag := range tags.Tags { fmt.Printf("用户标签: %s\n", tag.Name) } ``` CheckUserHasTag [#checkuserhastag] 通过标签代码快速检查用户是否拥有该标签。 ```go result, err := client.Merchant.CheckUserHasTag(ctx, &service.CheckUserHasTagRequest{ UserId: "user-123", TagCode: "vip", }) if result.HasTag { fmt.Println("用户是 VIP") } else { fmt.Println("用户不是 VIP") } ``` 公告系统 [#公告系统] GetRecentAnnouncements [#getrecentannouncements] 获取当前应用所属商户的最近公告(最多 10 条)。 **响应包含哈希值:** 返回结果包含 MD5 和 SHA256 哈希值,客户端可用于判断公告内容是否变化,决定是否需要重新展示。 ```go announcements, err := client.Merchant.GetRecentAnnouncements(ctx, &service.GetRecentAnnouncementsRequest{}) if err != nil { panic(err) } for _, ann := range announcements.Announcements { fmt.Printf("公告: %s\n", ann.Title) fmt.Printf(" MD5: %s\n", ann.Md5Hash) fmt.Printf(" SHA256: %s\n", ann.Sha256Hash) } ``` GetAnnouncement [#getannouncement] 根据公告 ID 获取详细信息。 ```go announcement, err := client.Merchant.GetAnnouncement(ctx, &service.GetAnnouncementRequest{ AnnouncementId: "ann-123", }) if err != nil { panic(err) } fmt.Printf("标题: %s\n", announcement.Title) fmt.Printf("内容: %s\n", announcement.Content) ``` 使用场景 [#使用场景] 用户权限控制 [#用户权限控制] ```go // 检查用户是否有管理员标签 func IsAdmin(ctx context.Context, client *bSdkClient.SsoClient, userId string) bool { result, err := client.Merchant.CheckUserHasTag(ctx, &service.CheckUserHasTagRequest{ UserId: userId, TagCode: "admin", }) if err != nil { return false } return result.HasTag } ``` 功能开关 [#功能开关] ```go // 根据用户标签决定是否启用某功能 func CanAccessPremiumFeature(ctx context.Context, client *bSdkClient.SsoClient, userId string) bool { tags, err := client.Merchant.GetUserTags(ctx, &service.GetUserTagsRequest{ UserId: userId, }) if err != nil { return false } for _, tag := range tags.Tags { if tag.Code == "premium" || tag.Code == "vip" { return true } } return false } ``` 公告缓存 [#公告缓存] ```go // 使用哈希值判断公告是否变化 type AnnouncementCache struct { LastHash string Content []Announcement } func GetAnnouncementsWithCache(ctx context.Context, client *bSdkClient.SsoClient, cache *AnnouncementCache) []Announcement { resp, _ := client.Merchant.GetRecentAnnouncements(ctx, &service.GetRecentAnnouncementsRequest{}) // 计算整体哈希 currentHash := calculateCombinedHash(resp.Announcements) if currentHash == cache.LastHash { // 内容未变化,使用缓存 return cache.Content } // 内容已变化,更新缓存 cache.LastHash = currentHash cache.Content = resp.Announcements return cache.Content } ``` # Public 服务 (/docs/beacon-sso/go-sdk/grpc/public-service) Public 服务 [#public-服务] Public 服务提供无需认证的公共功能,主要用于发送验证码。 接口概览 [#接口概览] | 方法 | 说明 | 认证 | | ----------------------- | --------- | -- | | `SendRegisterEmailCode` | 发送注册邮箱验证码 | 无 | SendRegisterEmailCode [#sendregisteremailcode] 发送注册邮箱验证码。 请求参数 [#请求参数] ```go type SendRegisterEmailCodeRequest struct { Email string // 目标邮箱地址 } ``` 响应 [#响应] ```go type SendRegisterEmailCodeResponse struct { // 基础响应信息 } ``` 使用示例 [#使用示例] ```go import ( "context" bSdkClient "github.com/phalanx-labs/beacon-sso-sdk/client" pb "github.com/phalanx-labs/beacon-sso-sdk/client/api/beacon/sso/v1" ) func main() { ctx := context.Background() // 创建客户端(Public 服务无需 App 凭证) client := bSdkClient.NewClient( bSdkClient.WithConnect("sso.example.com", "5566"), ) // 发送验证码 _, err := client.Public.SendRegisterEmailCode(ctx, &pb.SendRegisterEmailCodeRequest{ Email: "user@example.com", }) if err != nil { panic(err) } println("验证码已发送") } ``` 限制说明 [#限制说明] | 限制 | 说明 | | ---- | -------------------------------------------- | | 有效期 | 15 分钟(可通过 `EMAIL_VERIFY_CODE_EXPIRE` 环境变量配置) | | 频率限制 | 同一邮箱 1 分钟内只能发送一次 | 错误码 [#错误码] | 错误 | 说明 | | -------------------- | ------- | | `invalid_argument` | 邮箱格式无效 | | `resource_exhausted` | 发送频率超限 | | `unavailable` | 邮件服务不可用 | HTTP 路由 [#http-路由] SDK 同时提供 HTTP 路由封装: ```bash POST /api/account/register/email-code Content-Type: application/json { "email": "user@example.com" } ``` # User 服务 (/docs/beacon-sso/go-sdk/grpc/user-service) User 服务 [#user-服务] User 服务提供用户信息查询功能。 User 服务需要在 metadata 中提供 App 凭证和用户 Token。 接口概览 [#接口概览] | 方法 | 说明 | Token 来源 | | ---------------- | ------------ | ------------- | | `GetCurrentUser` | 获取当前登录用户信息 | 从 Token 中提取 | | `GetUserByID` | 根据用户 ID 获取信息 | 需要提供 user\_id | GetCurrentUser [#getcurrentuser] 获取当前登录用户的详细信息。 方法签名 [#方法签名] ```go func (s *UserService) GetCurrentUser( ctx context.Context, accessToken string, ) (*pb.GetCurrentUserResponse, error) ``` 响应 [#响应] ```go type GetCurrentUserResponse struct { UserId string // 用户 ID Username string // 用户名 Nickname string // 昵称 Email string // 邮箱 Phone string // 手机号 Avatar string // 头像 URL Status int32 // 状态 // ... 其他字段 } ``` 使用示例 [#使用示例] ```go import ( "context" bSdkClient "github.com/phalanx-labs/beacon-sso-sdk/client" ) func main() { ctx := context.Background() client := bSdkClient.NewClient( bSdkClient.WithConnect("sso.example.com", "5566"), bSdkClient.WithAppAccess("app-id", "app-secret"), ) // 假设 accessToken 来自登录响应或请求头 accessToken := "eyJhbGciOiJSUzI1NiIs..." // 获取当前用户信息 user, err := client.User.GetCurrentUser(ctx, accessToken) if err != nil { panic(err) } fmt.Printf("用户 ID: %s\n", user.UserId) fmt.Printf("用户名: %s\n", user.Username) fmt.Printf("邮箱: %s\n", user.Email) } ``` GetUserByID [#getuserbyid] 根据用户 ID 获取指定用户的详细信息。主要用于接入 App 需要获取其他用户信息的场景。 与 GetCurrentUser 的区别 [#与-getcurrentuser-的区别] | 方法 | 信息来源 | 用途 | | ---------------- | -------------- | ---------- | | `GetCurrentUser` | 从 Token 中获取 | 获取当前登录用户信息 | | `GetUserByID` | 通过 user\_id 参数 | 获取任意用户信息 | 方法签名 [#方法签名-1] ```go func (s *UserService) GetUserByID( ctx context.Context, accessToken string, req *pb.GetUserByIDRequest, ) (*pb.GetUserByIDResponse, error) ``` 请求参数 [#请求参数] ```go type GetUserByIDRequest struct { UserId string // 目标用户 ID } ``` 响应 [#响应-1] ```go type GetUserByIDResponse struct { UserId string // 用户 ID Username string // 用户名 Nickname string // 昵称 Email string // 邮箱 Phone string // 手机号 Avatar string // 头像 URL Status int32 // 状态 // ... 其他字段 } ``` 使用示例 [#使用示例-1] ```go // 获取指定用户信息 user, err := client.User.GetUserByID(ctx, accessToken, &pb.GetUserByIDRequest{ UserId: "target-user-123", }) if err != nil { panic(err) } fmt.Printf("用户名: %s\n", user.Username) ``` HTTP 路由 [#http-路由] SDK 提供以下 User 相关 HTTP 路由: | 方法 | 路径 | 认证 | 说明 | | --- | -------------------- | --------- | ------------ | | GET | `/api/user/userinfo` | CheckAuth | 获取当前用户信息 | | GET | `/api/user/by-id` | CheckAuth | 根据 ID 获取用户信息 | 请求示例 [#请求示例] ```bash # 获取当前用户信息 GET /api/user/userinfo HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIs... # 根据 ID 获取用户信息 GET /api/user/by-id?user_id=target-user-123 HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIs... ``` # 认证检查中间件 (/docs/beacon-sso/go-sdk/middleware/auth-check) CheckAuth 中间件 [#checkauth-中间件] `CheckAuth` 是一个 Gin 中间件,用于验证请求中的 Access Token 是否有效。 功能说明 [#功能说明] 该中间件执行以下验证流程: 1. 从请求头 `Authorization` 提取 Access Token 2. 调用 `OAuthLogic.VerifyExpiry()` 验证 Token 是否过期 3. 验证通过后将 Token 存入 Gin Context 4. 验证失败则中断请求并返回错误 使用方法 [#使用方法] 函数签名 [#函数签名] ```go func CheckAuth(ctx context.Context) gin.HandlerFunc ``` 全局使用 [#全局使用] ```go func main() { // ... 初始化代码 ... reg := xReg.Register(context.Background(), nodes) // 全局使用中间件 reg.Serve.Use(bSdkMiddleware.CheckAuth(reg.Init.Ctx)) // 所有路由都需要认证 reg.Serve.GET("/api/protected", protectedHandler) } ``` 路由组使用 [#路由组使用] ```go func main() { // ... 初始化代码 ... reg := xReg.Register(context.Background(), nodes) // 公开路由 reg.Serve.GET("/api/public", publicHandler) // 需要认证的路由组 protected := reg.Serve.Group("/api") protected.Use(bSdkMiddleware.CheckAuth(reg.Init.Ctx)) protected.GET("/user", userHandler) protected.GET("/profile", profileHandler) } ``` 单个路由使用 [#单个路由使用] ```go reg.Serve.GET("/api/sensitive", bSdkMiddleware.CheckAuth(reg.Init.Ctx), sensitiveHandler, ) ``` 工作流程 [#工作流程] 从 Context 获取 Token [#从-context-获取-token] 中间件验证通过后,Token 会被存入 Gin Context: ```go func protectedHandler(c *gin.Context) { // 获取已验证的 Access Token token, exists := c.Get("authorization") if !exists { c.JSON(500, gin.H{"error": "token not found"}) return } accessToken := token.(string) // 使用 token 调用其他服务... } ``` 错误响应 [#错误响应] | 错误码 | 说明 | HTTP 状态码 | | ---------------- | -------------------- | -------- | | `ParameterEmpty` | Authorization 头缺失或为空 | 400 | | `NotExist` | Token 在缓存中不存在 | 401 | | `TokenExpired` | Token 已过期 | 401 | 错误响应示例 [#错误响应示例] ```json { "code": 401, "message": "访问令牌已过期", "data": null } ``` 完整示例 [#完整示例] ```go package main import ( "context" xConsts "github.com/bamboo-services/bamboo-base-go/defined/context" xReg "github.com/bamboo-services/bamboo-base-go/major/register" xRegNode "github.com/bamboo-services/bamboo-base-go/major/register/node" xResult "github.com/bamboo-services/bamboo-base-go/major/result" "github.com/gin-gonic/gin" bSdkMiddleware "github.com/phalanx-labs/beacon-sso-sdk/middleware" bSdkStartup "github.com/phalanx-labs/beacon-sso-sdk/startup" ) func main() { nodes := []xRegNode.RegNodeList{ {Key: xConsts.DatabaseKey, Node: initDatabase}, {Key: xConsts.RedisClientKey, Node: initRedis}, } nodes = append(nodes, bSdkStartup.NewStartupConfig()...) reg := xReg.Register(context.Background(), nodes) // 公开路由 reg.Serve.GET("/api/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) // 需要认证的路由 protected := reg.Serve.Group("/api") protected.Use(bSdkMiddleware.CheckAuth(reg.Init.Ctx)) protected.GET("/me", func(c *gin.Context) { token, _ := c.Get("authorization") xResult.SuccessHasData(c, "获取成功", gin.H{ "token": token, }) }) _ = reg.Serve.Run(":8080") } ``` # 中间件 (/docs/beacon-sso/go-sdk/middleware) 中间件 [#中间件] SDK 提供 Gin 中间件,用于在请求处理前进行身份验证。 可用中间件 [#可用中间件] | 中间件 | 说明 | | ------------------------- | ----------- | | [CheckAuth](./auth-check) | Token 验证中间件 | 使用示例 [#使用示例] ```go import ( bSdkMiddleware "github.com/phalanx-labs/beacon-sso-sdk/middleware" ) // 全局使用 reg.Serve.Use(bSdkMiddleware.CheckAuth(reg.Init.Ctx)) // 路由组使用 api := reg.Serve.Group("/api") api.Use(bSdkMiddleware.CheckAuth(reg.Init.Ctx)) // 单个路由使用 reg.Serve.GET("/protected", bSdkMiddleware.CheckAuth(reg.Init.Ctx), handler, ) ``` # 授权码流程 (/docs/beacon-sso/go-sdk/oauth2/flow) 授权码流程 [#授权码流程] 本文档详细介绍 OAuth2 授权码流程的实现细节,包括 State 防护和 PKCE 安全机制。 流程概览 [#流程概览] OAuthLogic 核心方法 [#oauthlogic-核心方法] Create - 创建 State 和 PKCE [#create---创建-state-和-pkce] ```go func (l *OAuthLogic) Create(ctx context.Context) (*bSdkModels.CacheOAuth, *xError.Error) ``` 生成随机的 State 字符串和 PKCE Code Verifier,并存储到 Redis。 **返回值:** * `CacheOAuth.State` - 随机状态码(32 位大写字母) * `CacheOAuth.Verifier` - PKCE Code Verifier **缓存策略:** * Key: `oauth:state:{state}` * TTL: 15 分钟 BuildURL - 构建授权 URL [#buildurl---构建授权-url] ```go func (l *OAuthLogic) BuildURL(ctx context.Context, oAuth *bSdkModels.CacheOAuth) (string, *xError.Error) ``` 根据 State 和 Verifier 构建完整的 OAuth2 授权跳转 URL。 **PKCE 模式:** 使用 S256(SHA-256)Challenge 方法 **生成的 URL 示例:** ``` https://sso.example.com/oauth/authorize ?client_id=xxx &redirect_uri=http://localhost:8080/api/oauth/callback &response_type=code &state=ABCD1234... &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 ``` Verify - 验证 State [#verify---验证-state] ```go func (l *OAuthLogic) Verify(ctx context.Context, state string) (*bSdkModels.CacheOAuth, *xError.Error) ``` 从 Redis 中获取 State 对应的 PKCE Verifier,验证成功后删除缓存(防止重放攻击)。 **错误情况:** * State 为空 * Redis 中不存在该 State * Verifier 为空 Exchange - 用 Code 换 Token [#exchange---用-code-换-token] ```go func (l *OAuthLogic) Exchange(ctx context.Context, code string, verifier string) (*oauth2.Token, *xError.Error) ``` 使用授权码和 PKCE Verifier 向 SSO Server 请求 Access Token。 **自动缓存:** 成功获取 Token 后自动缓存到 Redis 安全机制 [#安全机制] State 防护 [#state-防护] State 参数用于防止 CSRF 攻击: 1. 用户发起登录时生成随机 State 2. 将 State 存储到 Redis 3. 回调时验证 State 是否匹配 4. 验证成功后立即删除(防重放) PKCE 增强 [#pkce-增强] PKCE (Proof Key for Code Exchange) 防止授权码劫持: 1. 生成随机 Code Verifier 2. 计算 Code Challenge = BASE64URL(SHA256(Verifier)) 3. 授权请求携带 Challenge 4. Token 请求携带 Verifier 5. 服务端验证 Verifier 与 Challenge 匹配 PKCE 是 OAuth2 推荐的安全增强措施,强烈建议始终启用。 直接使用 OAuthLogic [#直接使用-oauthlogic] 如果你需要自定义路由或处理逻辑,可以直接使用 OAuthLogic: ```go import ( "context" bSdkLogic "github.com/phalanx-labs/beacon-sso-sdk/logic" ) func handleLogin(c *gin.Context) { ctx := c.Request.Context() oauthLogic := bSdkLogic.NewOAuth(ctx) // 1. 创建 State 和 PKCE oauth, err := oauthLogic.Create(ctx) if err != nil { // 处理错误 return } // 2. 构建跳转 URL authURL, err := oauthLogic.BuildURL(ctx, oauth) if err != nil { // 处理错误 return } // 3. 重定向 c.Redirect(302, authURL) } func handleCallback(c *gin.Context) { ctx := c.Request.Context() code := c.Query("code") state := c.Query("state") oauthLogic := bSdkLogic.NewOAuth(ctx) // 1. 验证 State oauth, err := oauthLogic.Verify(ctx, state) if err != nil { // State 无效 return } // 2. 用 Code 换 Token token, err := oauthLogic.Exchange(ctx, code, oauth.Verifier) if err != nil { // 换取失败 return } // 使用 token.AccessToken... } ``` # OAuth2 子系统 (/docs/beacon-sso/go-sdk/oauth2) OAuth2 子系统 [#oauth2-子系统] OAuth2 子系统基于 `golang.org/x/oauth2` 官方库扩展,提供完整的授权码流程支持,包含 State 防护和 PKCE 安全增强。 核心特性 [#核心特性] * **授权码流程** - 标准 OAuth2 Authorization Code Flow * **State 防护** - 防止 CSRF 攻击 * **PKCE 增强** - S256 Challenge 模式,防止授权码劫持 * **Token 缓存** - 基于 Redis 的 Token 自动缓存 * **自动刷新** - 使用 Refresh Token 自动更新 Access Token 架构图 [#架构图] 核心组件 [#核心组件] | 组件 | 说明 | | ---------------- | --------------- | | `OAuthLogic` | OAuth 业务逻辑层 | | `OAuthRepo` | State/PKCE 数据仓储 | | `OAuthTokenRepo` | Token 数据仓储 | 子文档 [#子文档] 了解完整的 OAuth2 授权码流程,包括 State 验证和 PKCE 机制 了解 Token 的缓存、刷新和注销机制 查看 SDK 提供的默认 OAuth2 路由 # 默认路由 (/docs/beacon-sso/go-sdk/oauth2/routes) 默认路由 [#默认路由] SDK 提供开箱即用的 OAuth2 路由,可通过 `OAuthRouter` 快速挂载。 路由列表 [#路由列表] | 方法 | 路径 | 认证 | 说明 | | ---- | ----------------- | -- | --------------------------- | | GET | `/oauth/login` | 无 | 302 重定向到 SSO 授权页面 | | GET | `/oauth/callback` | 无 | OAuth2 回调,用 code 换 token | | POST | `/oauth/logout` | 无 | 注销 token(通过 Header 传 token) | 挂载路由 [#挂载路由] ```go import bSdkRoute "github.com/phalanx-labs/beacon-sso-sdk/route" // 挂载到 /api 前缀 sdkRoute := bSdkRoute.NewRoute(ctx) sdkRoute.OAuthRouter(reg.Serve.Group("/api")) // 或挂载到根路径 sdkRoute.OAuthRouter(reg.Serve.Group("")) ``` 路由详情 [#路由详情] GET /oauth/login [#get-oauthlogin] 发起 OAuth2 登录流程,重定向到 SSO 授权页面。 **请求示例:** ```bash GET /api/oauth/login HTTP/1.1 ``` **响应:** ``` HTTP/1.1 302 Found Location: https://sso.example.com/oauth/authorize?client_id=xxx&redirect_uri=xxx&response_type=code&state=xxx&code_challenge=xxx&code_challenge_method=S256 ``` GET /oauth/callback [#get-oauthcallback] OAuth2 回调端点,处理 SSO Server 的授权响应。 **请求参数:** | 参数 | 类型 | 说明 | | ------- | ------ | --------- | | `code` | string | 授权码 | | `state` | string | 状态码(用于验证) | **请求示例:** ```bash GET /api/oauth/callback?code=abc123&state=XYZ789 HTTP/1.1 ``` **成功响应:** ```json { "code": 200, "message": "登录成功", "data": { "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...", "expires_in": 3600 } } ``` **错误响应:** | 错误码 | 说明 | | --- | -------------------------- | | 401 | 未登录(State 验证失败或 Code 换取失败) | POST /oauth/logout [#post-oauthlogout] 注销当前用户的 Token。 **请求头:** | Header | 说明 | | --------------- | ---------------------- | | `Authorization` | Bearer `{accessToken}` | **请求示例:** ```bash POST /api/oauth/logout HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIs... ``` **成功响应:** ```json { "code": 200, "message": "注销成功" } ``` 自定义路由前缀 [#自定义路由前缀] 你可以自由选择路由前缀: ```go // 方式 1:挂载到 /api sdkRoute.OAuthRouter(reg.Serve.Group("/api")) // 路由: /api/oauth/login, /api/oauth/callback, /api/oauth/logout // 方式 2:挂载到 /auth sdkRoute.OAuthRouter(reg.Serve.Group("/auth")) // 路由: /auth/oauth/login, /auth/oauth/callback, /auth/oauth/logout // 方式 3:挂载到根路径 sdkRoute.OAuthRouter(reg.Serve.Group("")) // 路由: /oauth/login, /oauth/callback, /oauth/logout ``` 完整路由挂载示例 [#完整路由挂载示例] ```go package main import ( "context" xConsts "github.com/bamboo-services/bamboo-base-go/defined/context" xReg "github.com/bamboo-services/bamboo-base-go/major/register" xRegNode "github.com/bamboo-services/bamboo-base-go/major/register/node" "github.com/gin-gonic/gin" bSdkRoute "github.com/phalanx-labs/beacon-sso-sdk/route" bSdkStartup "github.com/phalanx-labs/beacon-sso-sdk/startup" ) func main() { // 初始化 nodes := []xRegNode.RegNodeList{ {Key: xConsts.DatabaseKey, Node: initDatabase}, {Key: xConsts.RedisClientKey, Node: initRedis}, } nodes = append(nodes, bSdkStartup.NewStartupConfig()...) reg := xReg.Register(context.Background(), nodes) // 挂载所有 SDK 路由 sdkRoute := bSdkRoute.NewRoute(reg.Init.Ctx) // OAuth2 路由(登录/回调/登出) sdkRoute.OAuthRouter(reg.Serve.Group("/api")) // 账户路由(注册/登录/刷新) sdkRoute.AccountRouter(reg.Serve.Group("/api")) // 用户路由(获取用户信息) sdkRoute.UserRouter(reg.Serve.Group("/api")) // 启动服务 _ = reg.Serve.Run(":8080") } ``` # Token 管理 (/docs/beacon-sso/go-sdk/oauth2/token) Token 管理 [#token-管理] 本文档介绍 Token 的缓存机制、刷新策略和注销流程。 Token 结构 [#token-结构] SDK 使用 `CacheOAuthToken` 模型存储 Token 信息: ```go type CacheOAuthToken struct { AccessToken string // 访问令牌 TokenType string // 令牌类型(通常为 "Bearer") RefreshToken string // 刷新令牌 Expiry string // 过期时间(RFC3339 格式) } ``` 缓存策略 [#缓存策略] | 缓存类型 | Redis Key 模板 | TTL | | ----------- | --------------------------- | ---- | | OAuth Token | `oauth:token:{accessToken}` | 30 天 | 缓存时机 [#缓存时机] Token 在以下情况下会被缓存: 1. **Exchange 成功后** - 授权码换取 Token 时自动缓存 2. **TokenSource 刷新后** - 刷新 Token 时更新缓存 3. **PasswordLogin 成功后** - 密码登录时自动缓存 Token 刷新 [#token-刷新] TokenSource 方法 [#tokensource-方法] ```go func (l *OAuthLogic) TokenSource( ctx context.Context, cacheToken *bSdkModels.CacheOAuthToken, rt string, ) (*oauth2.Token, *xError.Error) ``` 使用 Refresh Token 获取新的 Access Token。 **安全检查:** * 验证传入的 RT 与缓存中的 RT 是否一致 * 不一致时清理缓存并返回错误 **使用示例:** ```go oauthLogic := bSdkLogic.NewOAuth(ctx) // 从缓存获取 Token cacheToken, err := oauthLogic.GetToken(ctx, accessToken) if err != nil { // Token 不存在 return } // 刷新 Token newToken, err := oauthLogic.TokenSource(ctx, cacheToken, refreshToken) if err != nil { // 刷新失败 return } ``` 自动刷新 [#自动刷新] SDK 在路由层提供了 `/api/account/token/refresh` 接口,可以直接调用: ```bash POST /api/account/token/refresh Content-Type: application/json { "refresh_token": "your-refresh-token" } ``` Token 获取 [#token-获取] GetToken 方法 [#gettoken-方法] ```go func (l *OAuthLogic) GetToken(ctx context.Context, accessToken string) (*bSdkModels.CacheOAuthToken, *xError.Error) ``` 从 Redis 缓存中获取 Token 信息。 **用途:** 中间件验证用户身份时使用 Token 过期验证 [#token-过期验证] VerifyExpiry 方法 [#verifyexpiry-方法] ```go func (l *OAuthLogic) VerifyExpiry(ctx context.Context, accessToken string) (bool, *xError.Error) ``` 检查 Token 是否已过期。 **返回值:** * `true` - Token 已过期 * `false` - Token 未过期 **使用示例:** ```go oauthLogic := bSdkLogic.NewOAuth(ctx) isExpired, err := oauthLogic.VerifyExpiry(ctx, accessToken) if err != nil { // 获取 Token 失败 return } if isExpired { // Token 已过期,需要刷新 } ``` Token 注销 [#token-注销] Logout 方法 [#logout-方法] ```go func (l *OAuthLogic) Logout(ctx context.Context, tokenType string, token string) *xError.Error ``` 调用 OAuth2 Revocation Endpoint 注销 Token(符合 RFC 7009 规范)。 **参数:** * `tokenType` - 令牌类型提示(`access_token` 或 `refresh_token`) * `token` - 待注销的令牌值 **流程:** 1. 向 Revocation Endpoint 发送注销请求 2. 尝试清理本地 Redis 缓存 3. 缓存清理失败仅记录警告,不阻断流程 **使用示例:** ```go oauthLogic := bSdkLogic.NewOAuth(ctx) // 注销 Access Token err := oauthLogic.Logout(ctx, "access_token", accessToken) if err != nil { // 注销失败 } // 注销 Refresh Token err = oauthLogic.Logout(ctx, "refresh_token", refreshToken) ``` HTTP 路由 [#http-路由] SDK 提供以下 Token 相关路由: | 方法 | 路径 | 说明 | | ---- | ---------------------------- | --------------------- | | POST | `/api/oauth/logout` | 注销 Token(从 Header 获取) | | POST | `/api/account/token/refresh` | 刷新令牌 | | POST | `/api/account/token/revoke` | 注销令牌(需认证) | 注销请求示例 [#注销请求示例] ```bash # 从 Header 获取 Token 注销 POST /api/oauth/logout Authorization: Bearer your-access-token # 或者指定 Token 类型 POST /api/account/token/revoke Authorization: Bearer your-access-token Content-Type: application/json { "token_type_hint": "access_token" } ``` # 缓存策略 (/docs/beacon-sso/java-sdk/advanced/cache) 缓存策略 [#缓存策略] SDK 使用 Caffeine 进行高性能内存缓存,以提升性能和减少对 SSO Server 的请求。 缓存架构 [#缓存架构] 缓存配置 [#缓存配置] SDK 内置 6 个缓存实例,每个缓存有不同的用途和 TTL 配置: 缓存列表 [#缓存列表] | 缓存名称 | 说明 | TTL | 最大容量 | | ------------------- | ---------------------------- | ----- | ------ | | `oauthState` | OAuth2 State + PKCE Verifier | 15 分钟 | 10,000 | | `grpcUserinfo` | gRPC 用户信息 | 10 秒 | 10,000 | | `grpcMerchantTag` | 商户标签 | 10 秒 | 10,000 | | `grpcAnnouncement` | 公告信息 | 10 秒 | 10,000 | | `httpUserinfo` | HTTP 用户信息 | 10 秒 | 10,000 | | `httpIntrospection` | Token 自省结果 | 10 秒 | 10,000 | 缓存 Bean 名为 `beaconSsoCacheManager`,基于 Spring 的 `CaffeineCacheManager` 实现。所有缓存在应用重启后会自动失效。 与 Go SDK 的对比 [#与-go-sdk-的对比] Java SDK 使用 **Caffeine**(进程内内存缓存),Go SDK 使用 **Redis**(分布式缓存)。两者的缓存策略有本质区别: | 对比项 | Java SDK (Caffeine) | Go SDK (Redis) | | ----- | ------------------- | -------------- | | 存储位置 | JVM 进程内内存 | Redis 服务 | | 分布式支持 | 不支持(单实例) | 支持(跨实例共享) | | 缓存失效 | 进程重启即失效 | 基于 TTL 过期 | | 外部依赖 | 无 | 需要 Redis | | 数据一致性 | 各实例独立缓存 | 多实例共享同一份缓存 | 影响说明 [#影响说明] * **无分布式缓存失效**:各应用实例拥有独立的缓存副本,某个实例缓存的数据变更不会自动同步到其他实例 * **适用场景**:适合单实例部署或无状态部署(如 K8s Pod),每个实例独立维护缓存 * **TTL 较短**:大部分缓存 TTL 为 10 秒,确保数据不会长期不一致 OAuthState 实体 [#oauthstate-实体] OAuth2 流程中用于存储 State 和 PKCE 信息的缓存实体: | 字段 | 类型 | 说明 | | -------------- | ------- | ------------------ | | `state` | String | 随机状态码 | | `codeVerifier` | String | PKCE Code Verifier | | `redirectUri` | String | 回调地址 | | `createdAt` | Instant | 创建时间 | | `expiresAt` | Instant | 过期时间 | 方法 [#方法] | 方法 | 返回值 | 说明 | | ------------- | --------- | ------------------- | | `isExpired()` | `boolean` | 判断 OAuthState 是否已过期 | # 高级话题 (/docs/beacon-sso/java-sdk/advanced) 高级话题 [#高级话题] 本章节介绍 SDK 的高级配置和使用技巧。 子文档 [#子文档] 了解 SDK 的 Caffeine 内存缓存机制和配置选项 直接使用 SsoApi [#直接使用-ssoapi] 除了通过 Filter 和 AOP 的方式使用 SDK 外,你还可以直接注入 `SsoApi` Bean 进行编程式调用: ```java @RestController @RequestMapping("/api") public class DemoController { private final SsoApi ssoApi; public DemoController(SsoApi ssoApi) { this.ssoApi = ssoApi; } @GetMapping("/userinfo") public BaseResponse getUserinfo(HttpServletRequest request) { String token = SsoSecurityUtil.getCurrentToken(request).orElse(null); UserinfoResult result = ssoApi.user().getCurrentUser(token); return BaseResponse.success("获取成功", result); } } ``` 传输模式 [#传输模式] HTTP-only 模式(默认) [#http-only-模式默认] SDK 默认仅使用 HTTP 传输,适用于只需要 OAuth2 登录流程和 Token 自省的场景。无需额外配置 gRPC 参数。 gRPC 模式 [#grpc-模式] 启用 gRPC 后,SDK 会同时使用 HTTP(OAuth2 流程)和 gRPC(用户、认证、商户服务)两种传输方式: ```yaml beacon: sso: grpc: enabled: true host: sso.example.com port: 5566 app-access-id: app-xxx app-secret-key: secret-xxx ``` OAuth2 授权流程始终使用 HTTP 传输,gRPC 仅用于用户信息、账户管理和商户服务等内部服务调用。 # AOP 切面 (/docs/beacon-sso/java-sdk/aop) AOP 切面 [#aop-切面] SDK 提供 Spring AOP 注解,用于在 Controller 方法执行前进行参数注入和权限验证。 可用注解 [#可用注解] | 注解 | 说明 | | ---------------------------------------- | ------------------------- | | [@InjectData](./inject-data) | 自动注入用户数据到 Controller 方法参数 | | [@PermissionVerify](./permission-verify) | 基于 scope 的权限验证 | 前置条件 [#前置条件] AOP 切面依赖 `BeaconSsoFilter` 先完成 Token 认证。过滤器会将 `IntrospectResult` 存入 Request Attribute,AOP 切面再从中读取数据进行注入或权限校验。 请确保 `BeaconSsoFilter` 已正确配置且处于启用状态,否则 AOP 注解将无法获取到用户认证信息。 子文档 [#子文档] 自动注入 IntrospectResult 字段到 Controller 方法参数 基于 scope 的方法级权限验证 # @InjectData (/docs/beacon-sso/java-sdk/aop/inject-data) @InjectData [#injectdata] `@InjectData` 注解用于自动将 `IntrospectResult` 中的字段注入到 Controller 方法参数中,简化获取用户信息的代码。 功能说明 [#功能说明] 通过 `@InjectData` 注解,你可以直接在 Controller 方法参数上声明需要的用户数据字段,SDK 会自动从 `BeaconSsoFilter` 存入的 `IntrospectResult` 中提取对应值并注入。 支持的字段 [#支持的字段] | 字段 | 类型 | 说明 | | ----------- | ------- | -------------- | | `sub` | String | 用户唯一标识 | | `username` | String | 用户名 | | `clientId` | String | 客户端 ID | | `scope` | String | 权限范围 | | `tokenType` | String | 令牌类型 | | `iss` | String | 签发者 | | `jti` | String | 令牌 ID | | `exp` | Long | 过期时间(Unix 时间戳) | | `iat` | Long | 签发时间(Unix 时间戳) | | `nbf` | Long | 生效时间(Unix 时间戳) | | `aud` | String | 受众 | | `active` | Boolean | 令牌是否有效 | 类型转换 [#类型转换] SDK 支持以下类型自动转换: | 目标类型 | 转换规则 | | -------------- | --------------------------- | | `String` | 直接取值 | | `Boolean` | 字符串转布尔值 | | `Long` | 字符串转长整型(适用于时间戳字段) | | `List` | 逗号分隔的字符串转为列表(适用于 scope 等字段) | 注入整个 IntrospectResult [#注入整个-introspectresult] 如果不指定 `value()` 且参数类型为 `IntrospectResult`,SDK 会注入完整的自省结果对象。 使用示例 [#使用示例] ```java @RestController @RequestMapping("/api") public class DemoController { // 注入单个字段 @GetMapping("/profile") public BaseResponse getProfile(@InjectData("sub") String userId) { return BaseResponse.success("获取成功", userId); } // 注入完整对象 @GetMapping("/info") public BaseResponse getInfo(@InjectData IntrospectResult result) { return BaseResponse.success("获取成功", result); } // 注入多个字段 @GetMapping("/detail") public BaseResponse> getDetail( @InjectData("sub") String userId, @InjectData("username") String username, @InjectData("scope") List scope) { Map data = new HashMap<>(); data.put("userId", userId); data.put("username", username); data.put("scope", scope); return BaseResponse.success("获取成功", data); } } ``` required 参数 [#required-参数] `@InjectData` 提供 `required` 参数(默认为 `true`),控制当字段值不存在时的行为: ```java // 默认 required = true,字段不存在时抛出异常 @GetMapping("/strict") public BaseResponse strict(@InjectData("sub") String userId) { return BaseResponse.success("获取成功", userId); } // required = false,字段不存在时注入 null @GetMapping("/optional") public BaseResponse optional( @InjectData(value = "nickname", required = false) String nickname) { return BaseResponse.success("获取成功", nickname); } ``` # @PermissionVerify (/docs/beacon-sso/java-sdk/aop/permission-verify) @PermissionVerify [#permissionverify] `@PermissionVerify` 注解用于在 Controller 方法执行前验证当前用户是否拥有所需的权限(scope)。 功能说明 [#功能说明] 通过 `@PermissionVerify` 注解声明方法所需的权限,SDK 会在方法执行前自动检查当前用户的 Token scope 是否满足要求。若权限不足,直接返回 403 JSON 响应并抛出 `PermissionDeniedException`,方法不会被执行。 注解参数 [#注解参数] | 参数 | 类型 | 默认值 | 说明 | | ------------ | ---------- | -------- | --------------------------------- | | `value` | `String[]` | - | 所需的权限(scope)字符串 | | `requireAll` | `boolean` | `true` | 是否需要满足所有权限(true = AND,false = OR) | | `message` | `String` | `"权限不足"` | 权限不足时的错误提示信息 | 使用方式 [#使用方式] 方法级别 [#方法级别] 在 Controller 方法上使用: ```java @RestController @RequestMapping("/api/admin") @PermissionVerify(value = "admin", message = "需要管理员权限") public class AdminController { // 继承类级别的 admin 权限要求 @GetMapping("/dashboard") public BaseResponse dashboard() { return BaseResponse.success("ok", "管理面板"); } // 方法级别覆盖:需要 admin 或 settings 任一权限 @GetMapping("/settings") @PermissionVerify(value = {"admin", "settings"}, requireAll = false) public BaseResponse settings() { return BaseResponse.success("ok", "设置"); } } ``` 多权限组合 [#多权限组合] ```java @RestController @RequestMapping("/api") public class DemoController { // AND 模式:必须同时拥有 user:read 和 user:write @GetMapping("/user/edit") @PermissionVerify(value = {"user:read", "user:write"}) public BaseResponse editUser() { return BaseResponse.success("ok", "编辑用户"); } // OR 模式:拥有 admin 或 super_admin 任一即可 @GetMapping("/manage") @PermissionVerify(value = {"admin", "super_admin"}, requireAll = false) public BaseResponse manage() { return BaseResponse.success("ok", "管理"); } } ``` 类级别与方法级别 [#类级别与方法级别] 方法级别的 `@PermissionVerify` 会**覆盖**类级别的注解。也就是说,如果类和方法上都声明了该注解,只有方法级别的注解会生效。 推荐将通用的权限要求声明在类级别上,特殊的权限要求在方法级别覆盖。 权限不足响应 [#权限不足响应] 当权限验证不通过时,SDK 会向客户端写入 403 JSON 响应: ```json { "code": 403, "message": "权限不足", "data": null } ``` 如果指定了自定义 `message`,响应中的 `message` 字段会使用你设置的值。 # 错误处理 (/docs/beacon-sso/java-sdk/api/errors) 错误处理 [#错误处理] SDK 提供完整的异常体系,所有错误都携带对应的错误码信息,便于定位和处理。 异常体系 [#异常体系] ``` RuntimeException └── SsoException (基类,携带 SsoErrorCode) ├── SsoConfigurationException (配置异常,附带 configKey) │ ├── SsoConfigurationException.missing(key) │ └── SsoConfigurationException.invalid(key) └── TokenException (Token 异常,附带 tokenType) ├── tokenType: ACCESS_TOKEN ├── tokenType: REFRESH_TOKEN ├── tokenType: ID_TOKEN └── tokenType: AUTHORIZATION_CODE ``` SsoException [#ssoexception] 所有 SDK 异常的基类,携带 `SsoErrorCode` 错误码枚举。 SsoConfigurationException [#ssoconfigurationexception] 配置相关异常,在缺少必填配置或配置无效时抛出。提供静态工厂方法快速创建: ```java // 缺少配置 throw SsoConfigurationException.missing("beacon.sso.client-id"); // 配置无效 throw SsoConfigurationException.invalid("beacon.sso.base-url"); ``` TokenException [#tokenexception] Token 相关异常,携带 `tokenType` 字段标识异常来源的 Token 类型: | TokenType | 说明 | | -------------------- | ----------------------- | | `ACCESS_TOKEN` | Access Token 相关异常 | | `REFRESH_TOKEN` | Refresh Token 相关异常 | | `ID_TOKEN` | ID Token 相关异常 | | `AUTHORIZATION_CODE` | Authorization Code 相关异常 | 错误码 [#错误码] 错误码列表 [#错误码列表] | 错误码 | 说明 | | ---------------------- | ----------------- | | `INVALID_STATE` | OAuth State 无效或过期 | | `INVALID_CODE` | 授权码无效 | | `TOKEN_EXPIRED` | Access Token 过期 | | `TOKEN_INVALID` | Access Token 无效 | | `USERINFO_FAILED` | 获取用户信息失败 | | `INTROSPECTION_FAILED` | Token 自省失败 | | `PKCE_ERROR` | PKCE 生成或验证失败 | | `NETWORK_ERROR` | 网络请求失败 | | `CONFIGURATION_ERROR` | SDK 配置错误 | | `GRPC_NOT_ENABLED` | gRPC 未启用 | 错误响应格式 [#错误响应格式] SDK 的错误响应遵循统一的 JSON 格式: ```json { "code": 401, "message": "访问令牌已过期", "data": null } ``` 错误处理示例 [#错误处理示例] 基本用法 [#基本用法] ```java @RestController @RequestMapping("/api") public class DemoController { private final SsoApi ssoApi; public DemoController(SsoApi ssoApi) { this.ssoApi = ssoApi; } @GetMapping("/userinfo") public BaseResponse getUserinfo(HttpServletRequest request) { String token = SsoSecurityUtil.getCurrentToken(request).orElse(null); try { UserinfoResult result = ssoApi.user().getCurrentUser(token); return BaseResponse.success("获取成功", result); } catch (SsoException e) { // 根据 ErrorCode 处理不同错误 if (e.getErrorCode() == SsoErrorCode.TOKEN_EXPIRED) { return BaseResponse.error(401, "Token 已过期,请重新登录"); } if (e.getErrorCode() == SsoErrorCode.NETWORK_ERROR) { return BaseResponse.error(500, "网络异常,请稍后重试"); } return BaseResponse.error(500, e.getMessage()); } } } ``` 处理 Token 异常 [#处理-token-异常] ```java try { TokenResult result = ssoApi.oauth().refreshToken(refreshToken); } catch (TokenException e) { if (e.getTokenType() == TokenException.TokenType.REFRESH_TOKEN) { // Refresh Token 无效,需要重新登录 redirect("/oauth/login"); } } ``` 最佳实践 [#最佳实践] 1. **始终捕获 SsoException** — 在调用 SDK API 时,使用 try-catch 捕获 `SsoException`,根据 `ErrorCode` 进行差异化处理 2. **区分客户端和服务端错误** — Token 过期/无效属于 401 客户端错误,网络异常属于 500 服务端错误 3. **不暴露敏感信息** — 在返回给前端的错误消息中,不要包含 Token、Secret 等敏感信息 4. **合理使用日志** — 对 `NETWORK_ERROR`、`CONFIGURATION_ERROR` 等异常记录 Error 级别日志,便于排查问题 5. **优雅降级** — 在 gRPC 不可用时(`GRPC_NOT_ENABLED`),考虑使用 HTTP 回退方案 # API 参考 (/docs/beacon-sso/java-sdk/api) API 参考 [#api-参考] 本章节提供 SDK 的完整 API 参考。 子文档 [#子文档] 所有数据模型的字段说明 错误码与异常体系参考 核心包 [#核心包] | 包路径 | 说明 | | -------------------------------------------------------------- | -------------------------------------------------------------------------- | | `com.frontleaves.phalanx.beacon.sso.sdk.base.client` | SsoApi、SsoWebClient — 统一 API 入口与 HTTP 客户端 | | `com.frontleaves.phalanx.beacon.sso.sdk.base.api` | SsoOAuthApi、SsoAccountApi、SsoUserApi、SsoMerchantApi、SsoPublicApi — 子服务 API | | `com.frontleaves.phalanx.beacon.sso.sdk.base.config` | AutoConfiguration、BeaconSsoProperties — 自动配置与属性 | | `com.frontleaves.phalanx.beacon.sso.sdk.base.models` | Request/Result 数据模型 | | `com.frontleaves.phalanx.beacon.sso.sdk.base.exception` | 异常体系 | | `com.frontleaves.phalanx.beacon.sso.sdk.springboot.filter` | BeaconSsoFilter — Servlet 过滤器 | | `com.frontleaves.phalanx.beacon.sso.sdk.springboot.annotation` | @InjectData、@PermissionVerify — AOP 注解 | | `com.frontleaves.phalanx.beacon.sso.sdk.springboot.utility` | SsoSecurityUtil、SsoResponseUtil — 工具类 | # 数据模型 (/docs/beacon-sso/java-sdk/api/models) 数据模型 [#数据模型] 本页面列出 SDK 中所有核心数据模型。 OAuth 模型 [#oauth-模型] OAuthState [#oauthstate] OAuth2 State 和 PKCE Verifier 的缓存实体。 | 字段 | 类型 | 说明 | | -------------- | ------- | ------------------ | | `state` | String | 随机状态码 | | `codeVerifier` | String | PKCE Code Verifier | | `redirectUri` | String | 回调地址 | | `createdAt` | Instant | 创建时间 | | `expiresAt` | Instant | 过期时间 | **方法:** `isExpired()` — 判断是否已过期。 *** TokenResult [#tokenresult] OAuth2 Token 端点响应模型。 | 字段 | 类型 | 说明 | | -------------- | ------- | ------------ | | `accessToken` | String | 访问令牌 | | `tokenType` | String | 令牌类型(Bearer) | | `expiresIn` | Long | 过期时间(秒) | | `refreshToken` | String | 刷新令牌 | | `scope` | String | 权限范围 | | `createdAt` | Instant | 创建时间 | *** AuthorizationUrlResult [#authorizationurlresult] 授权 URL 构建结果。 | 字段 | 类型 | 说明 | | --------------- | ------ | ------------------- | | `url` | String | 完整的授权 URL | | `state` | String | State 参数 | | `codeChallenge` | String | PKCE Code Challenge | | `codeVerifier` | String | PKCE Code Verifier | *** 用户模型 [#用户模型] UserinfoResult [#userinforesult] OAuth2 Userinfo Endpoint 的响应模型。 | 字段 | 类型 | 说明 | | --------------- | ------- | ------- | | `sub` | String | 用户唯一标识 | | `name` | String | 用户名称 | | `email` | String | 邮箱 | | `emailVerified` | Boolean | 邮箱是否已验证 | | `picture` | String | 头像 URL | *** UserDetailResult [#userdetailresult] 用户详细信息模型(gRPC)。 | 字段 | 类型 | 说明 | | ------------------- | ----------------- | -------- | | `id` | String | 用户 ID | | `username` | String | 用户名 | | `nickname` | String | 昵称 | | `email` | String | 邮箱 | | `emailVerified` | Boolean | 邮箱是否已验证 | | `avatar` | String | 头像 URL | | `phone` | String | 手机号 | | `phoneVerified` | Boolean | 手机号是否已验证 | | `gender` | String | 性别 | | `birthday` | String | 生日 | | `status` | String | 账号状态 | | `needResetPassword` | Boolean | 是否需要重置密码 | | `lastLoginAt` | String | 最后登录时间 | | `lastLoginIp` | String | 最后登录 IP | | `roles` | List\ | 角色列表 | *** RoleResult [#roleresult] 用户角色模型。 | 字段 | 类型 | 说明 | | ------------- | ------ | ---- | | `code` | String | 角色编码 | | `name` | String | 角色名称 | | `description` | String | 角色描述 | *** Introspection 模型 [#introspection-模型] IntrospectResult [#introspectresult] OAuth2 Token Introspection 的响应模型。 | 字段 | 类型 | 说明 | | ----------- | ------- | -------------- | | `active` | Boolean | 令牌是否有效 | | `scope` | String | 权限范围 | | `clientId` | String | 客户端 ID | | `username` | String | 用户名 | | `tokenType` | String | 令牌类型 | | `exp` | Long | 过期时间(Unix 时间戳) | | `iat` | Long | 签发时间(Unix 时间戳) | | `nbf` | Long | 生效时间(Unix 时间戳) | | `sub` | String | 用户唯一标识 | | `aud` | String | 受众 | | `iss` | String | 签发者 | | `jti` | String | 令牌 ID | *** ValidateResult [#validateresult] Token 验证结果模型。 | 字段 | 类型 | 说明 | | --------- | ------- | ------ | | `valid` | Boolean | 是否有效 | | `message` | String | 验证结果消息 | *** Account 模型 [#account-模型] LoginResult [#loginresult] 登录结果模型。 | 字段 | 类型 | 说明 | | -------------- | ------ | -------- | | `accessToken` | String | 访问令牌 | | `tokenType` | String | 令牌类型 | | `expiresIn` | Long | 过期时间(秒) | | `refreshToken` | String | 刷新令牌 | | `scope` | String | 权限范围 | | `idToken` | String | ID Token | *** RegisterResult [#registerresult] 注册结果模型。 | 字段 | 类型 | 说明 | | -------- | ----------- | -------- | | `userId` | String | 用户 ID | | `token` | TokenResult | Token 信息 | *** Merchant 模型 [#merchant-模型] MerchantTagResult [#merchanttagresult] 商户标签模型。 | 字段 | 类型 | 说明 | | ------------- | ------- | ----- | | `id` | String | 标签 ID | | `code` | String | 标签编码 | | `name` | String | 标签名称 | | `description` | String | 标签描述 | | `color` | String | 标签颜色 | | `icon` | String | 标签图标 | | `sortOrder` | Integer | 排序顺序 | | `status` | String | 标签状态 | *** AnnouncementResult [#announcementresult] 公告模型。 | 字段 | 类型 | 说明 | | -------------- | ------ | ------ | | `id` | String | 公告 ID | | `title` | String | 公告标题 | | `content` | String | 公告内容 | | `scope` | String | 公告范围 | | `displayUntil` | String | 展示截止时间 | | `createdAt` | String | 创建时间 | *** RecentAnnouncementsResult [#recentannouncementsresult] 最近公告列表模型。 | 字段 | 类型 | 说明 | | --------------- | -------------------------- | ----- | | `announcements` | List\ | 公告列表 | | `meta` | AnnouncementListMetaResult | 列表元信息 | *** AnnouncementListMetaResult [#announcementlistmetaresult] 公告列表元信息模型。 | 字段 | 类型 | 说明 | | ------------- | ------- | ------------ | | `md5Hash` | String | 内容 MD5 哈希 | | `sha256Hash` | String | 内容 SHA256 哈希 | | `count` | Integer | 公告数量 | | `generatedAt` | String | 生成时间 | # 认证过滤器 (/docs/beacon-sso/java-sdk/filter/auth-filter) 认证过滤器 [#认证过滤器] `BeaconSsoFilter` 是 SDK 的核心过滤器,负责 Token 认证与用户信息注入。 工作原理 [#工作原理] `BeaconSsoFilter` 继承 Spring 的 `OncePerRequestFilter`,在每个 HTTP 请求中执行一次 Token 认证流程: 认证流程说明 [#认证流程说明] 1. 从请求头 `Authorization` 中提取 Bearer Token 2. 调用 `SsoApi.introspectToken()` 向 SSO Server 发起 Token 自省请求 3. 若 Token 有效(`active = true`),将 `IntrospectResult` 存入请求属性 `beacon.sso.introspection`,将 Access Token 存入 `beacon.sso.access_token`,继续过滤器链 4. 若无 Token 或 Token 无效,直接返回 401 JSON 响应 排除路径配置 [#排除路径配置] 通过 `beacon.sso.exclude-urls` 配置不需要认证的路径,支持 Ant 风格路径匹配: ```yaml beacon: sso: exclude-urls: - /api/public/** - /actuator/** - /health - /oauth/** ``` 路径匹配规则 [#路径匹配规则] | 模式 | 说明 | 匹配示例 | | ---- | -------- | ----------------------- | | `*` | 匹配任意单层路径 | `/api/public` | | `**` | 匹配任意多层路径 | `/api/public/v1/detail` | | `?` | 匹配任意单个字符 | `/api/public?` | SsoSecurityUtil 工具类 [#ssosecurityutil-工具类] `SsoSecurityUtil` 提供静态方法,用于从 `HttpServletRequest` 属性中获取认证信息: 方法列表 [#方法列表] | 方法 | 返回值 | 说明 | | ---------------------------------------------------- | ---------------------------- | --------------------- | | `SsoSecurityUtil.getCurrentToken(request)` | `Optional` | 获取当前请求的 Access Token | | `SsoSecurityUtil.getCurrentIntrospection(request)` | `Optional` | 获取当前请求的 Token 自省结果 | | `SsoSecurityUtil.isAuthenticated(request)` | `boolean` | 判断当前请求是否已认证 | | `SsoSecurityUtil.hasPermission(request, permission)` | `boolean` | 判断当前用户是否拥有指定权限(scope) | 使用示例 [#使用示例] ```java @RestController @RequestMapping("/api") public class DemoController { @GetMapping("/profile") public BaseResponse getProfile(HttpServletRequest request) { // 获取当前用户 ID String userId = SsoSecurityUtil.getCurrentIntrospection(request) .map(IntrospectResult::getSub) .orElse(null); // 获取当前 Token String token = SsoSecurityUtil.getCurrentToken(request).orElse(null); // 判断是否已认证 if (!SsoSecurityUtil.isAuthenticated(request)) { return BaseResponse.error(401, "未认证"); } // 判断权限 if (SsoSecurityUtil.hasPermission(request, "admin")) { return BaseResponse.success("管理员获取成功", userId); } return BaseResponse.success("获取成功", userId); } } ``` 错误响应 [#错误响应] 当认证失败时,`BeaconSsoFilter` 会返回如下格式的 401 JSON 响应: | 场景 | 说明 | | -------- | ----------------------------------- | | 无 Token | 请求头中未携带 `Authorization: Bearer xxx` | | Token 过期 | Token 已超过有效期 | | Token 无效 | Token 不被 SSO Server 识别或已被注销 | 错误响应示例 [#错误响应示例] ```json { "code": 401, "message": "未登录", "data": null } ``` # Filter 过滤器 (/docs/beacon-sso/java-sdk/filter) Filter 过滤器 [#filter-过滤器] SDK 提供 Servlet 过滤器,用于在请求处理前进行 Token 认证与注入。 可用过滤器 [#可用过滤器] | 过滤器 | 说明 | | -------------------------------- | ----------- | | [BeaconSsoFilter](./auth-filter) | Token 认证与注入 | 工作原理 [#工作原理] `BeaconSsoFilter` 继承 Spring 的 `OncePerRequestFilter`,确保在每个 HTTP 请求中只执行一次。通过 Spring Boot 自动配置机制自动注册,拦截所有请求(排除 `beacon.sso.exclude-urls` 配置的路径),从请求头中提取 Bearer Token 并调用 SSO Server 进行 Token 自省验证。 验证通过后,将认证信息存入 `HttpServletRequest` 的 Attribute 中,供后续 Controller 和 AOP 切面使用。 使用方式 [#使用方式] `BeaconSsoFilter` 由 Spring Boot 自动配置注册,无需手动注册。你只需引入 Starter 依赖并完成基础配置即可。 通过 `beacon.sso.exclude-urls` 排除不需要认证的路径: ```yaml beacon: sso: exclude-urls: - /api/public/** - /actuator/** - /health ``` 下一步 [#下一步] * [认证过滤器](./auth-filter) - 了解 `BeaconSsoFilter` 的详细工作流程与配置 # Auth 服务 (/docs/beacon-sso/java-sdk/grpc/auth-service) Auth 服务 [#auth-服务] Auth 服务提供用户认证相关功能,包括注册、登录、修改密码和注销令牌。 Auth 服务需要在 metadata 中提供有效的 App 凭证(`app-access-id` 和 `app-secret-key`),由 `GrpcUtil` 自动注入。 接口概览 [#接口概览] | 方法 | 说明 | 返回 Token | | ----------------- | ---- | -------- | | `RegisterByEmail` | 邮箱注册 | Yes | | `PasswordLogin` | 密码登录 | Yes | | `ChangePassword` | 修改密码 | No | | `RevokeToken` | 注销令牌 | No | RegisterByEmail [#registerbyemail] 通过邮箱验证码完成用户注册,注册成功后自动生成登录 Token。 请求参数 [#请求参数] | 参数 | 类型 | 必填 | 说明 | | ---------- | -------- | --- | ----- | | `email` | `string` | Yes | 邮箱地址 | | `password` | `string` | Yes | 密码 | | `username` | `string` | Yes | 用户名 | | `code` | `string` | Yes | 邮箱验证码 | | `nickname` | `string` | No | 昵称 | 响应 [#响应] | 字段 | 类型 | 说明 | | -------------- | -------- | ------- | | `userId` | `string` | 用户 ID | | `accessToken` | `string` | 访问令牌 | | `refreshToken` | `string` | 刷新令牌 | | `expiresIn` | `int64` | 过期时间(秒) | 使用示例 [#使用示例] ```java // 1. 先发送验证码 ssoApi.pub().sendRegisterEmailCode( SendRegisterEmailCodeRequest.newBuilder() .setEmail("user@example.com") .build() ); // 2. 用户输入验证码后注册 var resp = ssoApi.account().registerByEmail( RegisterByEmailRequest.newBuilder() .setEmail("user@example.com") .setCode("123456") .setUsername("newuser") .setPassword("SecurePass123!") .setNickname("New User") .build() ); System.out.printf("注册成功!用户 ID: %s%n", resp.getUserId()); System.out.printf("Access Token: %s%n", resp.getAccessToken()); ``` PasswordLogin [#passwordlogin] 实现 OAuth 2.0 Resource Owner Password Credentials Grant,允许受信任的第一方客户端直接使用用户名和密码换取 Token。 安全限制 [#安全限制] * **仅限第一方应用**(`App.FirstParty = enabled`) * 支持用户名/邮箱/手机号三种登录方式(自动识别) 请求参数 [#请求参数-1] | 参数 | 类型 | 必填 | 说明 | | ----------- | -------- | --- | ------------------------------ | | `username` | `string` | Yes | 用户名/邮箱/手机号 | | `password` | `string` | Yes | 密码 | | `scope` | `string` | No | 权限范围(如 `openid profile email`) | | `clientIp` | `string` | No | 客户端 IP 地址 | | `userAgent` | `string` | No | 客户端 User-Agent | 响应 [#响应-1] | 字段 | 类型 | 说明 | | -------------- | -------- | ------------------ | | `accessToken` | `string` | 访问令牌 | | `tokenType` | `string` | 令牌类型(通常为 `Bearer`) | | `expiresIn` | `int64` | 过期时间(秒) | | `refreshToken` | `string` | 刷新令牌 | | `scope` | `string` | 实际授权范围 | | `idToken` | `string` | ID Token(可选) | 使用示例 [#使用示例-1] ```java var resp = ssoApi.account().passwordLogin( PasswordLoginRequest.newBuilder() .setUsername("user@example.com") .setPassword("SecurePass123!") .setScope("openid profile email") .build() ); if (resp == null) { throw new RuntimeException("登录失败"); } System.out.println("登录成功!"); System.out.printf("Access Token: %s%n", resp.getAccessToken()); System.out.printf("过期时间: %d 秒%n", resp.getExpiresIn()); ``` 密码登录会自动缓存 Token,可通过 `AuthLogic` 相关方法获取。 ChangePassword [#changepassword] 修改用户密码,支持普通模式和强制重置模式。 模式说明 [#模式说明] | 模式 | 条件 | 旧密码 | | ------ | ------------------------- | --- | | 普通模式 | `NeedResetPassword=false` | 必填 | | 强制重置模式 | `NeedResetPassword=true` | 可省略 | 请求参数 [#请求参数-2] | 参数 | 类型 | 必填 | 说明 | | ------------- | -------- | --- | ----------- | | `userId` | `string` | Yes | 用户 ID | | `oldPassword` | `string` | 视模式 | 旧密码(普通模式必填) | | `newPassword` | `string` | Yes | 新密码 | 使用示例 [#使用示例-2] ```java // 普通模式修改密码 ssoApi.account().changePassword( ChangePasswordRequest.newBuilder() .setUserId("user-123") .setOldPassword("OldPass123!") .setNewPassword("NewSecurePass456!") .build() ); // 强制重置模式(管理员场景,无需旧密码) ssoApi.account().changePassword( ChangePasswordRequest.newBuilder() .setUserId("user-123") .setNewPassword("NewSecurePass456!") .build() ); ``` RevokeToken [#revoketoken] 注销用户 Token,实现用户登出功能。符合 RFC 7009 OAuth 2.0 Token Revocation 规范。 使用场景 [#使用场景] * 用户主动登出 * Token 泄露后的紧急注销 请求参数 [#请求参数-3] | 参数 | 类型 | 必填 | 说明 | | --------------- | -------- | --- | ---------------------------------------- | | `token` | `string` | Yes | 需要注销的 Token | | `tokenTypeHint` | `string` | No | 令牌类型提示(`access_token` / `refresh_token`) | 使用示例 [#使用示例-3] ```java // 注销 Access Token ssoApi.account().revokeToken( RevokeTokenRequest.newBuilder() .setToken(accessToken) .setTokenTypeHint("access_token") .build() ); // 注销 Refresh Token ssoApi.account().revokeToken( RevokeTokenRequest.newBuilder() .setToken(refreshToken) .setTokenTypeHint("refresh_token") .build() ); ``` SDK 的 HTTP 路由层调用 `RevokeToken` 后会自动清理本地缓存。 HTTP 路由 [#http-路由] SDK 提供以下 Auth 相关 HTTP 路由(需启用 gRPC): | 方法 | 路径 | 说明 | | ---- | -------------------------- | --------- | | POST | `/account/register/email` | 邮箱注册 | | POST | `/account/login/password` | 密码登录 | | POST | `/account/password/change` | 修改密码(需认证) | | POST | `/account/token/revoke` | 注销令牌(需认证) | Auth 路由依赖 gRPC 服务,需配置 `beacon.sso.grpc.enabled=true` 及相关连接参数。 # gRPC 服务 (/docs/beacon-sso/java-sdk/grpc) gRPC 服务 [#grpc-服务] Java SDK 使用标准 [gRPC](https://grpc.io/) 协议提供认证、用户、商户和公共四大服务。与 Go SDK 不同,Java SDK 采用原生 gRPC Stub 而非 Connect-RPC。 协议说明 [#协议说明] SDK 使用标准 gRPC 框架,基于 HTTP/2 传输,具有以下特点: * 原生 gRPC Stub,支持双向流式通信 * 基于 Protocol Buffers 序列化,高性能低延迟 * 通过 `ManagedChannel` 管理连接生命周期 可选启用 [#可选启用] gRPC 服务默认**关闭**,需手动启用。若未启用,gRPC 专属方法将抛出 `SsoConfigurationException(GRPC_NOT_ENABLED)` 异常。 启用配置 [#启用配置] 在 `application.yml` 中添加 gRPC 配置: ```yaml beacon: sso: grpc: enabled: true host: sso.example.com port: 5566 app-access-id: app-xxx app-secret-key: secret-xxx ``` 若 gRPC 未启用,调用 gRPC 专属方法将抛出 `SsoConfigurationException(GRPC_NOT_ENABLED)` 异常。 SsoApi 入口 [#ssoapi-入口] SDK 通过 `SsoApi` Facade 统一暴露所有服务,通过 Spring IoC 注入使用: ```java @Autowired private SsoApi ssoApi; // 账户服务(gRPC) ssoApi.account().registerByEmail(request); // 用户服务(gRPC 优先,HTTP 回退) ssoApi.user().getCurrentUser(accessToken); // 商户服务(gRPC) ssoApi.merchant().getMerchantTags(); // 公共服务(gRPC) ssoApi.pub().sendRegisterEmailCode(request); ``` 服务概览 [#服务概览] | 服务 | 说明 | 认证要求 | | --------------------------------- | ------------- | ------------------- | | [Auth 服务](./auth-service) | 注册、登录、改密、注销令牌 | App 凭证 | | [User 服务](./user-service) | 获取用户信息 | App 凭证 + User Token | | [Merchant 服务](./merchant-service) | 商户标签与公告 | App 凭证 | | [Public 服务](./public-service) | 验证码发送 | 无 | 认证机制 [#认证机制] App 凭证 [#app-凭证] Auth、User、Merchant 服务需要在 gRPC metadata 中提供 App 凭证: ```java // GrpcUtil 自动注入以下 metadata: // app-access-id: your-app-access-id // app-secret-key: your-app-secret-key ``` 凭证通过 `beacon.sso.grpc.app-access-id` 和 `beacon.sso.grpc.app-secret-key` 配置,由 `GrpcUtil` 在每次调用时自动附加,无需手动处理。 User Token [#user-token] User 服务还需要在 metadata 中提供用户 Token: ```java // 调用方法时传入 accessToken 参数 ssoApi.user().getCurrentUser(accessToken); ``` 服务封装层会自动处理 metadata 注入,你只需在调用时传入 `accessToken` 参数。 使用示例 [#使用示例] ```java import com.frontleaves.phalanx.beacon.sso.api.SsoApi; import org.springframework.beans.factory.annotation.Autowired; @Service public class DemoService { @Autowired private SsoApi ssoApi; public void demo() { // 使用 Public 服务(发送验证码) ssoApi.pub().sendRegisterEmailCode( SendRegisterEmailCodeRequest.newBuilder() .setEmail("user@example.com") .build() ); // 使用 Auth 服务(密码登录) var loginResp = ssoApi.account().passwordLogin( PasswordLoginRequest.newBuilder() .setUsername("user@example.com") .setPassword("password123") .setScope("openid profile email") .build() ); // 使用 User 服务(需要 accessToken) var userResp = ssoApi.user().getCurrentUser(loginResp.getAccessToken()); // 使用 Merchant 服务 var tagsResp = ssoApi.merchant().getMerchantTags(); } } ``` 子文档 [#子文档] 注册、登录、修改密码、注销令牌 获取当前用户、按 ID 查询 商户标签、公告管理 发送注册验证码等公共功能 # Merchant 服务 (/docs/beacon-sso/java-sdk/grpc/merchant-service) Merchant 服务 [#merchant-服务] Merchant 服务提供商户标签和公告管理功能。 Merchant 服务需要在 metadata 中提供 App 凭证(`app-access-id` 和 `app-secret-key`),由 `GrpcUtil` 自动注入。 接口概览 [#接口概览] | 方法 | 说明 | | ------------------------ | ----------- | | `GetMerchantTags` | 获取商户标签列表 | | `GetUserTags` | 获取用户标签列表 | | `CheckUserHasTag` | 检查用户是否有指定标签 | | `GetRecentAnnouncements` | 获取最近公告列表 | | `GetAnnouncement` | 获取单个公告详情 | 标签系统 [#标签系统] GetMerchantTags [#getmerchanttags] 获取当前应用所属商户的所有标签列表。 请求参数 [#请求参数] | 参数 | 类型 | 必填 | 说明 | | ------------- | ------ | -- | -------------------- | | `enabledOnly` | `bool` | No | 仅返回已启用的标签(默认 `true`) | 响应 [#响应] 返回 `MerchantTagResult` 列表: | 字段 | 类型 | 说明 | | ------------- | -------- | ---- | | `code` | `string` | 标签代码 | | `name` | `string` | 标签名称 | | `description` | `string` | 标签描述 | | `enabled` | `bool` | 是否启用 | 使用示例 [#使用示例] ```java var tags = ssoApi.merchant().getMerchantTags( GetMerchantTagsRequest.newBuilder() .setEnabledOnly(true) .build() ); if (tags != null) { for (var tag : tags.getTagsList()) { System.out.printf("标签: %s (%s)%n", tag.getName(), tag.getCode()); } } ``` GetUserTags [#getusertags] 获取指定用户在当前商户的所有标签。 请求参数 [#请求参数-1] | 参数 | 类型 | 必填 | 说明 | | ------------- | -------- | --- | -------------------- | | `userId` | `string` | Yes | 目标用户 ID | | `enabledOnly` | `bool` | No | 仅返回已启用的标签(默认 `true`) | 响应 [#响应-1] 返回 `MerchantTagResult` 列表,字段同 `GetMerchantTags`。 使用示例 [#使用示例-1] ```java var tags = ssoApi.merchant().getUserTags( GetUserTagsRequest.newBuilder() .setUserId("user-123") .setEnabledOnly(true) .build() ); if (tags != null) { for (var tag : tags.getTagsList()) { System.out.printf("用户标签: %s%n", tag.getName()); } } ``` CheckUserHasTag [#checkuserhastag] 通过标签代码快速检查用户是否拥有该标签。 请求参数 [#请求参数-2] | 参数 | 类型 | 必填 | 说明 | | --------- | -------- | --- | ------- | | `userId` | `string` | Yes | 目标用户 ID | | `tagCode` | `string` | Yes | 标签代码 | 响应 [#响应-2] | 字段 | 类型 | 说明 | | -------- | ------ | --------- | | `hasTag` | `bool` | 用户是否拥有该标签 | 使用示例 [#使用示例-2] ```java var result = ssoApi.merchant().checkUserHasTag( CheckUserHasTagRequest.newBuilder() .setUserId("user-123") .setTagCode("vip") .build() ); if (result != null && result.getHasTag()) { System.out.println("用户是 VIP"); } else { System.out.println("用户不是 VIP"); } ``` 公告系统 [#公告系统] GetRecentAnnouncements [#getrecentannouncements] 获取当前应用所属商户的最近公告(最多 10 条)。 **响应包含哈希值:** 返回结果包含 MD5 和 SHA256 哈希值,客户端可用于判断公告内容是否变化,决定是否需要重新展示。 请求参数 [#请求参数-3] | 参数 | 类型 | 必填 | 说明 | | ------------ | ------- | -- | -------------------- | | `limit` | `int32` | No | 最大返回数量(默认 10) | | `activeOnly` | `bool` | No | 仅返回生效中的公告(默认 `true`) | 响应 [#响应-3] `RecentAnnouncementsResult` 包含以下字段: | 字段 | 类型 | 说明 | | ----------------- | -------- | ------------- | | `announcements` | `list` | 公告列表 | | `meta.md5Hash` | `string` | 整体 MD5 哈希值 | | `meta.sha256Hash` | `string` | 整体 SHA256 哈希值 | 使用示例 [#使用示例-3] ```java var announcements = ssoApi.merchant().getRecentAnnouncements( GetRecentAnnouncementsRequest.newBuilder() .build() ); if (announcements != null) { System.out.printf("MD5: %s%n", announcements.getMeta().getMd5Hash()); System.out.printf("SHA256: %s%n", announcements.getMeta().getSha256Hash()); for (var ann : announcements.getAnnouncementsList()) { System.out.printf("公告: %s%n", ann.getTitle()); } } ``` GetAnnouncement [#getannouncement] 根据公告 ID 获取详细信息。 请求参数 [#请求参数-4] | 参数 | 类型 | 必填 | 说明 | | ---------------- | -------- | --- | ----- | | `announcementId` | `string` | Yes | 公告 ID | 响应 [#响应-4] `AnnouncementResult` 包含以下字段: | 字段 | 类型 | 说明 | | -------------- | -------- | ------ | | `id` | `string` | 公告 ID | | `title` | `string` | 公告标题 | | `content` | `string` | 公告内容 | | `scope` | `string` | 公告范围 | | `displayUntil` | `string` | 展示截止时间 | 使用示例 [#使用示例-4] ```java var announcement = ssoApi.merchant().getAnnouncement( GetAnnouncementRequest.newBuilder() .setAnnouncementId("ann-123") .build() ); if (announcement != null) { System.out.printf("标题: %s%n", announcement.getTitle()); System.out.printf("内容: %s%n", announcement.getContent()); } ``` 使用场景 [#使用场景] 用户权限控制 [#用户权限控制] ```java // 检查用户是否有管理员标签 public boolean isAdmin(String userId) { var result = ssoApi.merchant().checkUserHasTag( CheckUserHasTagRequest.newBuilder() .setUserId(userId) .setTagCode("admin") .build() ); return result != null && result.getHasTag(); } ``` 功能开关 [#功能开关] ```java // 根据用户标签决定是否启用某功能 public boolean canAccessPremiumFeature(String userId) { var tags = ssoApi.merchant().getUserTags( GetUserTagsRequest.newBuilder() .setUserId(userId) .build() ); if (tags == null) { return false; } return tags.getTagsList().stream() .anyMatch(tag -> "premium".equals(tag.getCode()) || "vip".equals(tag.getCode())); } ``` 公告缓存 [#公告缓存] ```java // 使用哈希值判断公告是否变化 public List getAnnouncementsWithCache(String cachedHash) { var resp = ssoApi.merchant().getRecentAnnouncements( GetRecentAnnouncementsRequest.newBuilder().build() ); if (resp == null) { return Collections.emptyList(); } String currentHash = resp.getMeta().getMd5Hash(); if (currentHash.equals(cachedHash)) { // 内容未变化,使用缓存 return Collections.emptyList(); } // 内容已变化,返回新数据并更新缓存 return resp.getAnnouncementsList(); } ``` HTTP 路由 [#http-路由] SDK 提供以下 Merchant 相关 HTTP 路由: | 方法 | 路径 | 说明 | | --- | ------------------------------------------ | -------- | | GET | `/merchant/tags` | 获取商户标签列表 | | GET | `/merchant/users/{userId}/tags` | 获取用户标签列表 | | GET | `/merchant/announcements` | 获取最近公告列表 | | GET | `/merchant/announcements/{announcementId}` | 获取公告详情 | # Public 服务 (/docs/beacon-sso/java-sdk/grpc/public-service) Public 服务 [#public-服务] Public 服务提供无需认证的公共功能,主要用于发送验证码。 接口概览 [#接口概览] | 方法 | 说明 | 认证 | | ----------------------- | --------- | -- | | `SendRegisterEmailCode` | 发送注册邮箱验证码 | 无 | SendRegisterEmailCode [#sendregisteremailcode] 发送注册邮箱验证码。 请求参数 [#请求参数] | 参数 | 类型 | 必填 | 说明 | | --------- | -------- | --- | --------- | | `email` | `string` | Yes | 目标邮箱地址 | | `purpose` | `string` | No | 用途(默认为注册) | 响应 [#响应] 基础响应信息,无额外数据字段。 使用示例 [#使用示例] ```java // 发送验证码 ssoApi.pub().sendRegisterEmailCode( SendRegisterEmailCodeRequest.newBuilder() .setEmail("user@example.com") .build() ); System.out.println("验证码已发送"); ``` 限制说明 [#限制说明] | 限制 | 说明 | | ---- | -------------------------------------------- | | 有效期 | 15 分钟(可通过 `EMAIL_VERIFY_CODE_EXPIRE` 环境变量配置) | | 频率限制 | 同一邮箱 1 分钟内只能发送一次 | 错误码 [#错误码] | 错误 | 说明 | | -------------------- | ------- | | `INVALID_ARGUMENT` | 邮箱格式无效 | | `RESOURCE_EXHAUSTED` | 发送频率超限 | | `UNAVAILABLE` | 邮件服务不可用 | HTTP 路由 [#http-路由] SDK 同时提供 HTTP 路由封装: ```bash POST /public/register/email/code Content-Type: application/json { "email": "user@example.com" } ``` # User 服务 (/docs/beacon-sso/java-sdk/grpc/user-service) User 服务 [#user-服务] User 服务提供用户信息查询功能,支持获取当前登录用户和按 ID 查询指定用户。 User 服务需要在 metadata 中提供 App 凭证和用户 Token,由 SDK 自动处理。 接口概览 [#接口概览] | 方法 | 说明 | Token 来源 | 传输方式 | | ---------------- | ------------ | ------------- | --------------- | | `GetCurrentUser` | 获取当前登录用户信息 | 从参数中传入 | gRPC 优先,HTTP 回退 | | `GetUserByID` | 根据用户 ID 获取信息 | 需要提供 user\_id | 仅 gRPC | GetCurrentUser [#getcurrentuser] 获取当前登录用户的详细信息。当 gRPC 启用时优先使用 gRPC 传输;当 gRPC 未启用时自动回退到 HTTP 方式。 请求参数 [#请求参数] 无需额外请求参数,通过 `accessToken` 标识当前用户。 响应 [#响应] `UserDetailResult` 包含以下字段: | 字段 | 类型 | 说明 | | ----------- | -------- | ------ | | `id` | `string` | 用户 ID | | `username` | `string` | 用户名 | | `nickname` | `string` | 昵称 | | `email` | `string` | 邮箱 | | `phone` | `string` | 手机号 | | `avatar` | `string` | 头像 URL | | `status` | `int32` | 状态 | | `roles` | `list` | 角色列表 | | `createdAt` | `string` | 创建时间 | | `updatedAt` | `string` | 更新时间 | 使用示例 [#使用示例] ```java // 假设 accessToken 来自登录响应或请求头 String accessToken = "eyJhbGciOiJSUzI1NiIs..."; // 获取当前用户信息 var user = ssoApi.user().getCurrentUser(accessToken); if (user == null) { throw new RuntimeException("获取用户信息失败"); } System.out.printf("用户 ID: %s%n", user.getId()); System.out.printf("用户名: %s%n", user.getUsername()); System.out.printf("邮箱: %s%n", user.getEmail()); ``` GetUserByID [#getuserbyid] 根据用户 ID 获取指定用户的详细信息。主要用于接入 App 需要获取其他用户信息的场景。 与 GetCurrentUser 的区别 [#与-getcurrentuser-的区别] | 方法 | 信息来源 | 传输方式 | 用途 | | ---------------- | ------------ | --------------- | ---------- | | `GetCurrentUser` | 从 Token 中获取 | gRPC 优先,HTTP 回退 | 获取当前登录用户信息 | | `GetUserByID` | 通过 userId 参数 | 仅 gRPC | 获取任意用户信息 | 请求参数 [#请求参数-1] | 参数 | 类型 | 必填 | 说明 | | -------- | -------- | --- | ------- | | `userId` | `string` | Yes | 目标用户 ID | 响应 [#响应-1] 与 `GetCurrentUser` 相同,返回 `UserDetailResult`。 使用示例 [#使用示例-1] ```java // 获取指定用户信息 var user = ssoApi.user().getUserById( accessToken, GetUserByIdRequest.newBuilder() .setUserId("target-user-123") .build() ); if (user == null) { throw new RuntimeException("获取用户信息失败"); } System.out.printf("用户名: %s%n", user.getUsername()); ``` `GetUserByID` 仅在 gRPC 启用时可用。若 gRPC 未启用,将抛出 `SsoConfigurationException(GRPC_NOT_ENABLED)` 异常。 HTTP 路由 [#http-路由] SDK 提供以下 User 相关 HTTP 路由: | 方法 | 路径 | 认证 | 说明 | | --- | ---------------- | --------- | ------------ | | GET | `/user/userinfo` | CheckAuth | 获取当前用户信息 | | GET | `/user/{userId}` | CheckAuth | 根据 ID 获取用户信息 | 请求示例 [#请求示例] ```bash # 获取当前用户信息 GET /user/userinfo HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIs... # 根据 ID 获取用户信息 GET /user/target-user-123 HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIs... ``` # 授权码流程 (/docs/beacon-sso/java-sdk/oauth2/flow) 授权码流程 [#授权码流程] 本文档详细介绍 OAuth2 授权码流程的实现细节,包括 PKCE 安全增强机制和 State 防护策略。 流程概览 [#流程概览] PKCE 安全机制 [#pkce-安全机制] PKCE (Proof Key for Code Exchange) 用于防止授权码劫持攻击,遵循 [RFC 7636](https://tools.ietf.org/html/rfc7636) 规范。 Code Verifier [#code-verifier] * 长度为 **43 字符**的随机字符串 * 使用 Base64url 编码(无填充) * 由 `PkceUtil` 自动生成 Code Challenge [#code-challenge] * 计算方式:`BASE64URL(SHA256(code_verifier))` * 方法标识:`S256` * 随授权请求一同发送至 SSO Server 验证流程 [#验证流程] 1. 登录时生成 `code_verifier` 并计算 `code_challenge` 2. 授权请求携带 `code_challenge` + `code_challenge_method=S256` 3. 回调时使用 `code_verifier` 向 SSO Server 交换 Token 4. SSO Server 验证 `verifier` 与 `challenge` 匹配 PKCE 是 OAuth2 推荐的安全增强措施,强烈建议始终启用。Java SDK 默认启用 PKCE。 State 管理 [#state-管理] State 参数用于防止 CSRF 攻击: | 特性 | 说明 | | ------ | -------------------------------------- | | 生成方式 | `StateUtil` 基于 `SecureRandom` 生成 | | 存储位置 | Caffeine 内存缓存 | | 缓存 TTL | 15 分钟 | | 验证方式 | 常量时间比较(`MessageDigest.isEqual`),防止时序攻击 | | 防重放 | 验证成功后立即从缓存中移除 | 与 Go SDK 使用 Redis 存储不同,Java SDK 使用 Caffeine 内存缓存,无需外部依赖,适合单实例部署场景。 OAuthUrlBuilder [#oauthurlbuilder] `OAuthUrlBuilder` 负责构建各类 OAuth2 URL,所有端点路径均可通过 `beacon.sso.endpoints.*` 配置覆盖: | 方法 | 用途 | 默认端点 | | ------------- | ------------ | ------------------ | | 构建授权 URL | 用户登录重定向 | `/oauth/authorize` | | 构建 Token URL | 用授权码换取 Token | `/oauth/token` | | 构建 Logout URL | Token 注销 | `/oauth/revoke` | **生成的授权 URL 示例:** ``` https://sso.example.com/oauth/authorize ?client_id=xxx &redirect_uri=http://localhost:8080/oauth/callback &response_type=code &state=ABCD1234... &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 ``` HTTP Session Token 存储 [#http-session-token-存储] 与 Go SDK 使用 Redis 缓存 Token 不同,Java SDK 将 Token 存储在 **HTTP Session** 中: * **存储时机**:OAuth2 回调成功后,Token 写入当前用户的 HTTP Session * **存储内容**:`TokenResult`(access\_token、refresh\_token、token\_type、expires\_in 等) * **读取方式**:通过 `HttpServletRequest.getSession()` 获取 * **清除时机**:用户注销(`/oauth/logout`)或 Session 过期时清除 HTTP Session 存储是 Java SDK 的独特设计,利用 Servlet 容器内置的会话管理机制,无需引入 Redis 等外部中间件,适合单实例或无状态部署场景。 # OAuth2 子系统 (/docs/beacon-sso/java-sdk/oauth2) OAuth2 子系统 [#oauth2-子系统] OAuth2 子系统基于 Spring Boot 自动配置实现,提供完整的授权码流程支持,包含 State 防护、PKCE 安全增强和 Caffeine 高性能缓存。 核心特性 [#核心特性] * **OAuth2 授权码流程** - 标准 Authorization Code Flow,集成 PKCE (S256) 安全增强 * **Caffeine State 缓存** - 基于 Caffeine 的高性能内存缓存,替代 Redis 依赖 * **HTTP Session Token 存储** - 利用 Servlet HTTP Session 存储 Token(无需外部存储) * **Token 刷新/注销** - 支持 Refresh Token 自动刷新和 RFC 7009 标准注销 * **OAuthUrlBuilder URL 构建** - 统一的授权、Token、注销 URL 构建器 架构图 [#架构图] 核心组件 [#核心组件] | 组件 | 说明 | | ----------------- | ------------------------------------------- | | `AuthLogic` | OAuth 业务逻辑层,处理登录、回调、Token 管理等核心流程 | | `PkceUtil` | PKCE Code Verifier / Challenge 生成工具 | | `StateUtil` | 基于 `SecureRandom` 的 State 生成工具 | | `OAuthUrlBuilder` | 统一构建授权 URL、Token URL、注销 URL | | `SsoOAuthApi` | 封装 HTTP 请求,与 SSO Server 通信 | | `Caffeine Cache` | 高性能内存缓存,存储 State + PKCE Verifier(15 分钟 TTL) | | `HTTP Session` | Servlet 标准会话存储,用于保存 Token 信息 | 子文档 [#子文档] 了解完整的 OAuth2 授权码流程,包括 State 验证和 PKCE 安全机制 了解 Token 的存储、刷新和注销机制 查看 SDK 自动注册的 OAuth2 与账户路由 # 默认路由 (/docs/beacon-sso/java-sdk/oauth2/routes) 默认路由 [#默认路由] SDK 通过 Spring Boot 自动配置机制自动注册 REST Controller,开箱即用,无需手动挂载路由。 OAuth 路由 [#oauth-路由] 前缀由 `beacon.sso.oauth-path` 配置,默认为 `/oauth`: | 方法 | 路径 | 认证 | 说明 | | --- | ----------------- | -- | ----------------------------- | | GET | `/oauth/login` | 无 | 302 重定向到 SSO 授权页面 | | GET | `/oauth/callback` | 无 | OAuth2 回调,验证 State + 交换 Token | | GET | `/oauth/logout` | 无 | 撤销 Token + 清除 HTTP Session | | GET | `/oauth/status` | 无 | 获取当前认证状态 | 账户路由 [#账户路由] 前缀由 `beacon.sso.account-path` 配置,默认为 `/account`。**账户路由依赖 gRPC 服务**,需额外配置 `beacon.sso.grpc.enabled=true`: | 方法 | 路径 | 认证 | 说明 | | ---- | -------------------------- | --- | -------- | | POST | `/account/register/email` | 无 | 邮箱注册 | | POST | `/account/login/password` | 无 | 密码登录 | | POST | `/account/password/change` | 需认证 | 修改密码 | | POST | `/account/logout` | 需认证 | 撤销 Token | 账户路由依赖 gRPC 服务,需在 `application.yml` 中配置 `beacon.sso.grpc.enabled=true` 及相关连接参数(host、port、app-access-id、app-secret-key)。 可配置路径前缀 [#可配置路径前缀] SDK 自动注册的所有 Controller 路径前缀均可通过配置自定义: | 配置项 | 默认值 | 影响路由 | | ------------------------- | ---------- | ----------- | | `beacon.sso.oauth-path` | `/oauth` | 登录/回调/登出/状态 | | `beacon.sso.account-path` | `/account` | 注册/登录/改密/登出 | 配置示例 [#配置示例] 将 OAuth 路由前缀改为 `/sso`,账户路由前缀改为 `/auth`: ```yaml beacon: sso: oauth-path: /sso account-path: /auth ``` 生成的路由 [#生成的路由] | 原路径 | 新路径 | | -------------------------- | ----------------------- | | `/oauth/login` | `/sso/login` | | `/oauth/callback` | `/sso/callback` | | `/oauth/logout` | `/sso/logout` | | `/oauth/status` | `/sso/status` | | `/account/register/email` | `/auth/register/email` | | `/account/login/password` | `/auth/login/password` | | `/account/password/change` | `/auth/password/change` | | `/account/logout` | `/auth/logout` | 路由详情 [#路由详情] GET /oauth/login [#get-oauthlogin] 发起 OAuth2 登录流程,重定向到 SSO 授权页面。 **请求示例:** ```bash GET /oauth/login HTTP/1.1 ``` **响应:** ``` HTTP/1.1 302 Found Location: https://sso.example.com/oauth/authorize?client_id=xxx&redirect_uri=xxx&response_type=code&state=xxx&code_challenge=xxx&code_challenge_method=S256 ``` GET /oauth/callback [#get-oauthcallback] OAuth2 回调端点,处理 SSO Server 的授权响应。 **请求参数:** | 参数 | 类型 | 说明 | | ------- | ------ | --------- | | `code` | string | 授权码 | | `state` | string | 状态码(用于验证) | **请求示例:** ```bash GET /oauth/callback?code=abc123&state=XYZ789 HTTP/1.1 ``` **成功响应:** SDK 将 Token 存入 HTTP Session 并重定向到应用首页(默认 `/`): ``` HTTP/1.1 302 Found Location: / ``` **错误响应:** | 错误码 | 说明 | | --- | ------------------ | | 401 | State 验证失败或授权码换取失败 | GET /oauth/logout [#get-oauthlogout] 注销当前用户,撤销 Token 并清除 Session。 **请求示例:** ```bash GET /oauth/logout HTTP/1.1 ``` **成功响应:** ``` HTTP/1.1 302 Found Location: / ``` GET /oauth/status [#get-oauthstatus] 获取当前用户的认证状态,从 HTTP Session 中读取 Token 信息。 **请求示例:** ```bash GET /oauth/status HTTP/1.1 ``` **已认证响应:** ```json { "code": 200, "message": "已认证", "data": { "authenticated": true, "accessToken": "eyJhbGciOiJSUzI1NiIs...", "tokenType": "Bearer", "expiresIn": 3600 } } ``` **未认证响应:** ```json { "code": 200, "message": "未认证", "data": { "authenticated": false } } ``` # Token 管理 (/docs/beacon-sso/java-sdk/oauth2/token) Token 管理 [#token-管理] 本文档介绍 Token 的数据模型、存储方式、刷新策略和注销流程。 TokenResult 模型 [#tokenresult-模型] SDK 使用 `TokenResult` 模型表示 Token 信息: | 字段 | 类型 | 说明 | | -------------- | -------- | ------------------------ | | `accessToken` | `String` | 访问令牌,用于 API 请求认证 | | `tokenType` | `String` | 令牌类型,固定为 `Bearer` | | `expiresIn` | `Long` | 访问令牌有效期(秒) | | `refreshToken` | `String` | 刷新令牌,用于获取新的 Access Token | | `scope` | `String` | 授权范围 | | `createdAt` | `Long` | Token 创建时间戳 | Token 存储方式 [#token-存储方式] Java SDK 使用 **HTTP Session** 存储 Token(OAuth2 回调成功后自动写入): | 特性 | 说明 | | ---- | ---------------------------------------- | | 存储位置 | Servlet HTTP Session | | 存储时机 | OAuth2 回调成功、密码登录成功 | | 读取方式 | `request.getSession().getAttribute(...)` | | 清除时机 | 用户注销或 Session 过期 | 与 Go SDK 使用 Redis 缓存不同,Java SDK 利用 Servlet 容器内置的 HTTP Session 机制管理 Token,无需引入外部存储依赖。 Token 刷新 [#token-刷新] 使用 `AuthLogic.refreshToken()` 方法通过 Refresh Token 获取新的 Access Token: ```java @Autowired private AuthLogic authLogic; TokenResult newToken = authLogic.refreshToken(refreshToken); ``` **流程说明:** 1. 使用 Refresh Token 向 SSO Server 发起刷新请求 2. SSO Server 验证 Refresh Token 有效性 3. 返回新的 `TokenResult`(包含新的 Access Token 和 Refresh Token) 4. 新 Token 写入当前 HTTP Session Token 撤销 [#token-撤销] SDK 提供 `AuthLogic.revokeToken()` 方法,遵循 [RFC 7009](https://tools.ietf.org/html/rfc7009) 规范注销 Token: ```java @Autowired private AuthLogic authLogic; // 注销指定 Token(默认为 access_token) authLogic.revokeToken(token); // 指定 Token 类型注销 authLogic.revokeToken(token, "refresh_token"); ``` **参数说明:** | 参数 | 说明 | | ----------- | ------------------------------------------- | | `token` | 待注销的令牌值 | | `tokenType` | 令牌类型提示:`access_token` 或 `refresh_token`(可选) | **流程说明:** 1. 向 SSO Server 的 `/oauth/revoke` 端点发送注销请求 2. SSO Server 标记该 Token 为已撤销 3. 清除本地 HTTP Session 中的 Token 数据 Token 验证 [#token-验证] SDK 通过 `UserLogic` 提供两种 Token 验证方式: Token 自省(Introspection) [#token-自省introspection] ```java @Autowired private UserLogic userLogic; IntrospectResult result = userLogic.introspectToken(accessToken); ``` 调用 SSO Server 的自省端点,返回 Token 的详细信息(是否有效、所属用户、过期时间等)。 Token 验证(Validation) [#token-验证validation] ```java ValidateResult result = userLogic.validateToken(accessToken); ``` 验证 Token 是否有效,返回简化的验证结果。 `BeaconSsoFilter` 内部会自动调用 Token 自省接口验证每个请求的 Bearer Token,并将结果存入 Request Attribute,供 `@InjectData` 注解使用。 HTTP 路由参考 [#http-路由参考] SDK 提供以下 Token 相关路由: | 方法 | 路径 | 说明 | | ---- | ----------------- | -------------------- | | GET | `/oauth/logout` | 注销 Token 并清除 Session | | GET | `/oauth/status` | 获取当前认证状态 | | POST | `/account/logout` | 注销 Token(gRPC,需认证) |