多租户应用集成Google Calendar API:按子域分离OAuth2令牌问题
这问题我之前做类似Slack的多租户SaaS项目时刚好碰到过,核心是要把用户身份+租户上下文牢牢绑定到OAuth令牌的全流程里,才能实现不同子域租户的权限隔离。下面是我实践下来可行的完整方案:
1. 授权请求阶段注入租户标识
Google OAuth的state参数是专门用来传递自定义上下文的,这是我们绑定租户的关键:
- 当用户在
foo.domain.com发起Google Calendar授权时,构造的授权URL里要带上state参数,格式可以是{租户标识}|{随机CSRF值},比如state=foo|abc123xyz。这里的foo就是对应子域的租户唯一ID,随机CSRF值是为了防止跨站请求伪造,必须加。 - 等Google授权完成回调到你的应用时,你就能从
state参数里解析出当前的租户ID,明确这个授权流程是属于哪个子域的。
2. 按「用户ID+租户ID」维度存储令牌
绝对不能只按用户ID存储令牌——因为同一个用户属于多个租户,每个租户的令牌必须完全独立:
- 你的数据库令牌表要包含这些核心字段:
user_id(用户唯一标识)、tenant_id(对应子域的租户ID)、access_token、refresh_token、expires_at(令牌过期时间)。 - 比如用户John在
foo.domain.com和bar.domain.com都授权后,数据库里会有两条独立的记录:一条是user_id=john_123&tenant_id=foo,另一条是user_id=john_123&tenant_id=bar,各自对应不同的令牌对。
3. API调用阶段按租户上下文取对应令牌
当用户在某个子域操作时,必须先锁定当前租户的上下文,再获取对应的令牌:
- 第一步:从当前请求的子域(比如
foo.domain.com)解析出tenant_id=foo,这一步要做严格校验,防止用户通过参数篡改租户ID(比如不能让用户手动传入tenant_id,必须从子域自动解析)。 - 第二步:结合当前登录用户的
user_id,去令牌表中查询匹配user_id和tenant_id的记录。 - 第三步:如果查询到的access token已经过期,就用该记录对应的refresh token去Google刷新令牌,然后更新该租户下的令牌记录——绝对不能用A租户的refresh token去刷新B租户的令牌,因为它们是完全独立的。
4. 额外的权限隔离保障
为了确保万无一失,还可以做这些补充:
- 避免跨租户令牌泄露:在应用的权限控制层,要确保任何接口都不能返回其他租户的令牌信息,哪怕是同一个用户的也不行。比如John在
foo.domain.com操作时,应用只能返回他在foo租户下的令牌,不能返回bar租户的。 - 可选:按租户配置OAuth权限范围:如果不同租户需要的Google Calendar权限不同(比如有的只需要读,有的需要写),可以在授权请求时,针对不同租户传入不同的
scope参数,同时在令牌表里也存储对应的scope字段,后续API调用时校验权限范围。 - 令牌失效处理:如果某个租户的用户取消了对应用的授权,要及时删除该租户下的令牌记录,避免后续无效调用。
5. 用户跨租户授权的体验优化
为了提升用户体验,还可以做这些细节:
- 当用户在新租户操作时,如果已经用同一个Google账号授权过你的应用,Google会直接跳过授权确认页面,但你还是要为这个新租户创建独立的令牌记录。
- 在用户的个人设置页面,显示他们已授权的所有租户,允许用户单独取消某个租户的授权(删除对应令牌记录)。
内容的提问来源于stack exchange,提问作者Greg Olsen
相关产品推荐
相关产品推荐

