使用 Casdoor 实现钉钉、企业微信、飞书、百度信息流和微信 OAuth 登录
Casdoor 内置了针对钉钉、企业微信(WeChat Work)、飞书(Lark)、百度信息流和微信的 OAuth 提供商。你在各厂商的开发者控制台创建应用,将其凭证粘贴到 Casdoor 提供商中,并将该提供商添加到你的应用。Casdoor 执行厂商流程(桌面端扫码,或在厂商客户端内登录),在需要时获取企业访问令牌,并将厂商的用户 ID 映射为 Casdoor 用户。你的应用仅与 Casdoor 进行 OIDC 通信,不会看到钉钉头部信息、企业微信 user_ticket 或微信 unionid。对于钉钉、企业微信和飞书,同步器还可以在首次登录前导入整个组织目录。
一览
| 提供商 | 你创建的应用 | 用户登录位置 | 关联 Casdoor 用户的 ID | 邮箱和手机号 | 目录同步器 |
|---|---|---|---|---|---|
| 钉钉 | 内部应用 (AppKey, AppSecret) | 钉钉登录页面, login.dingtalk.com | openId; unionid 作为用户名 | 来自个人资料,被企业目录数据覆盖;视为已验证 | 是 |
| 企业微信 | 自建应用 (CorpID, Secret, AgentId),或第三方应用 | Web 登录(扫码,或从企业微信桌面端快速登录)或在企业微信内静默登录 | userid(内部),open_userid(第三方) | 仅在具有 snsapi_privateinfo 权限时获取邮箱;不读取手机号 | 是 |
| Lark / 飞书 | 自定义应用 (App ID, App Secret) | Lark 登录页面,飞书或 Lark 全球版 | open_id; 用户名为 user_id,否则为 union_id,再否则为 open_id | 如果应用拥有通讯录权限,则支持 | 是 |
| 百度信息流 | 内部应用 (CorpID, Secret, AgentID) | 信息流授权页面 | userid,同时也作为用户名 | 邮箱 | 否 |
| 微信 | 开放平台上的网站应用,以及公众号 | PC 端扫码;通过公众号在微信浏览器内登录 | unionid,否则为 openid | 均不支持 | 否 |
每个提供商的设置步骤详见文档:钉钉、企业微信、Lark、信息流 和 微信。本文解释了每个提供商背后的工作原理。
你的应用看到的内容
表中所有提供商的最终结果相同。Casdoor 交换厂商的代码,在组织中找到或创建用户,然后向你的应用颁发自己的授权码。你的应用在 Casdoor 的令牌端点兑换该授权码,并获得由应用证书签名的 Casdoor ID 令牌和访问令牌。声明(claims)来自 Casdoor 用户记录:姓名、显示名称、邮箱、电话、头像、群组。
对于这五个提供商,你的后端代码是相同的。任何 OIDC 库均可使用;参见 OAuth 2.0 和 OIDC 参考,如需完整教程,请参见 Spring Boot SSO with OIDC。如果你之后确实需要调用厂商自身的 API,Casdoor 会将提供商的访问令牌存储在用户上,详见 OAuth 提供商。
钉钉
在钉钉开放平台创建内部应用,并将其 AppKey 和 AppSecret 复制到提供商的 Client ID 和 Client secret 中。登录页面将用户重定向到 https://login.dingtalk.com/oauth2/auth,携带 scope=openid 和 prompt=login consent。用户确认后,Casdoor 会发起四次调用:
POST https://api.dingtalk.com/v1.0/oauth2/userAccessToken将 code 转换为用户访问令牌。GET https://api.dingtalk.com/v1.0/contact/users/me返回用户的openId、unionId、昵称、头像、邮箱和手机号。这需要应用具备 Contact.User.Read 权限。POST https://api.dingtalk.com/v1.0/oauth2/accessToken携带appKey和appSecret,返回企业(应用)访问令牌。- 使用该令牌,
topapi/user/getbyunionid将unionid映射为企业userid,而topapi/v2/user/get读取目录记录。
如果用户不是拥有该应用的组织的成员,第 4 步将失败并返回钉钉错误 60121,登录终止。如果目录查找成功,目录中的邮箱和手机号将替换个人数据,unionid 成为用户名,职位作为额外声明保留。
企业访问令牌及其头部
钉钉有两代 API,它们在不同位置接受相同的令牌:
GET https://api.dingtalk.com/v1.0/contact/users/me
x-acs-dingtalk-access-token: <token>
POST https://oapi.dingtalk.com/topapi/v2/user/get?access_token=<token>
Content-Type: application/json
{"userid": "<userid>"}
api.dingtalk.com 上的新 API 仅从 x-acs-dingtalk-access-token 头部读取令牌。如果不带该头部(或使用 Authorization: Bearer)调用,它会回答 AuthenticationFailed.MissingParameter,并指出缺失的头部名称。旧的 oapi.dingtalk.com 端点仍然接受 access_token 查询参数。Casdoor 两者都用:users/me 使用头部,两个 topapi 调用使用查询参数,并在两处都使用来自 v1.0/oauth2/accessToken 的企业令牌。钉钉文档说明该令牌有效期为 7,200 秒,并要求调用者按应用缓存它。钉钉还将旧的 gettoken 端点标记为新应用的遗留接口。
Casdoor 将钉钉返回的邮箱和电话视为已验证,因此默认绑定规则(先邮箱,后电话)可以将钉钉登录关联到现有用户。参见 Casdoor 如何链接账户。
企业微信 (WeCom)
企业微信提供商有一个 Sub type 和一个 Method:
- Internal 与 Normal 组合会将用户重定向到企业微信网页登录页
login.work.weixin.qq.com/wwlogin/sso/login,参数为login_type=CorpApp。企业微信会显示二维码,或者在企业微信桌面端运行且已登录时提供一键快速登录。 - Internal 与 Silent 组合使用
open.weixin.qq.com/connect/oauth2/authorize,并带上应用的agentid。这是在企微客户端内打开页面时应使用的配置。 - Third-party 使用
login_type=ServiceApp和服务提供商密钥 (get_provider_token),然后调用get_login_info,并通过open_userid关联用户。
For internal apps, Casdoor gets the corporate token from cgi-bin/gettoken with the CorpID and the app Secret, then exchanges the code for the member's userid. A visitor who is not a member comes back with an openid instead, and Casdoor rejects the sign-in. The token is a query parameter on every WeCom call; WeCom says it normally lasts 7,200 seconds and that gettoken is rate limited.
电子邮件地址是需要重点规划的部分。对于在 2022-06-20 20:00 之后创建的自建应用,企业微信停止通过成员读取 API (user/get) 返回头像、性别、手机号和邮箱。要获取邮箱,需将提供商的 scope 设置为 snsapi_privateinfo。随后企业微信会返回一个 user_ticket,Casdoor 会在 getuserdetail 接口处兑换该票据以获取详情。企业微信要求管理员在应用设置中选择敏感字段,并且需要成员同意授权,且这些字段永远不会提供给第三方应用。Casdoor 不会从企业微信读取电话号码。
Lark 和飞书
在 飞书开放平台 或 Lark 全球平台上创建自定义应用,并复制 App ID 和 App Secret。对于 Lark(open.larksuite.com),请在提供商中开启 Use global endpoint;对于飞书(open.feishu.cn),请保持关闭。
Casdoor 首先从 auth/v3/tenant_access_token/internal 获取 tenant_access_token,然后在 Authorization: Bearer 头中包含该令牌,并将 code 发布到 authen/v1/access_token。响应包含 open_id、union_id、user_id、email、enterprise email 和 mobile。只有当应用拥有相应的权限时,飞书才会返回敏感信息:
| 字段 | 飞书权限 |
|---|---|
user_id | contact:user.employee_id:readonly |
email | contact:user.email:readonly |
enterprise_email | contact:user.employee:readonly |
mobile | contact:user.phone:readonly |
如果没有这些权限,登录仍然有效:用户名会从 user_id 回退到 union_id,再回退到 open_id。当 email 为空时,Casdoor 会使用 enterprise_email,并将 email 和 phone 均视为已验证。虽然飞书现在将 v1 access_token 端点列为历史版本,但它仍在提供服务。
百度如流
Infoflow (如流) 是百度的办公应用。在 Infoflow 管理后台注册应用,授予通讯录权限,并将 CorpID、Secret 和 AgentID 复制到提供商中,子类型设为 Internal。登录页面指向 https://xpc.im.baidu.com/oauth2/authorize。随后 Casdoor 使用 CorpID 和 Secret 调用 qy.im.baidu.com/api/gettoken,在 api/user/getuserinfo 处使用 code 和 AgentID 交换成员的 userid,并从 api/user/get 读取姓名、头像和邮箱。userid 同时用作关联 ID 和用户名,邮箱被视为已验证。目前没有 Infoflow 同步器,因此用户在首次登录时创建。
微信
微信登录分为两部分,通常两者都需要:
- Web 子类型:微信开放平台上的网站应用。PC 端登录页面显示来自
open.weixin.qq.com/connect/qrconnect的二维码,参数为scope=snsapi_login;用户使用微信应用扫描二维码。 - Mobile 子类型,或同一提供商的第二组凭证:公众号。在微信浏览器内,二维码无用,因此 Casdoor 切换到
connect/oauth2/authorize,参数为snsapi_userinfo和公众号的 AppID。如果未配置公众号,Casdoor 会提示用户在外部浏览器中打开页面。
WeChat returns no email or phone, only openid, unionid, nickname and avatar. Casdoor links the user by unionid when WeChat sends one, falling back to openid, and also stores the per-app openid. WeChat gives a user the same unionid across all apps and Official Accounts under one Open Platform account, so bind your website app and your Official Account to the same Open Platform account. Otherwise a user who scans on a PC and later signs in on a phone becomes two Casdoor users.
先导入目录
对于钉钉、企业微信和 Lark,同步器 可以按计划拉取用户和部门,从而在任何人登录之前创建账户,并将部门转换为 Casdoor 群组。每个同步器都使用来自供应商目录 API 的企业令牌:DingTalk syncer 递归遍历部门,WeCom syncer 对于 2022-08-15 之后创建的应用回退使用 user/list_id 和 user/get,而 Lark syncer 有一个 Lark platform 设置用于选择飞书或 Lark 全球版。
WeCom 同步器写入 WeCom 提供商用于关联的 userid,因此同步的用户可以直接登录。DingTalk 和 Lark 同步器存储目录中的 userid / user_id,而登录则通过 openId / open_id 进行关联。它们的首次登录是通过 email 或 phone 的绑定规则来匹配的,这之所以可行,是因为这两个提供商都将这些信息报告为已验证。
二维码登录和账户恢复
扫码登录将密码问题转移到了手机端:如果用户丢失了微信或钉钉账号,就无法进行扫码。由于这些身份只是关联到 Casdoor 用户而非直接等同于该用户,只要应用配置了邮件或短信提供商且用户留有相关联系方式,用户仍可通过验证码登录或使用忘记密码流程重置密码。钉钉和飞书会从目录中自动填充这些信息;对于微信,请在注册时要求用户提供手机号码。若要在页面上显示多个此类选项,请参阅登录方式。
参考资料
- 钉钉:获取用户令牌(
userAccessToken,expireIn7200):https://open.dingtalk.com/document/isvapp-server/obtain-user-token - 钉钉:获取内部应用的访问令牌:https://open.dingtalk.com/document/development/obtain-orgapp-token
- 钉钉:获取用户个人信息:https://open.dingtalk.com/document/orgapp-server/tutorial-obtaining-user-personal-information
- 企业微信:获取 access_token:https://developer.work.weixin.qq.com/document/path/91039
- 企业微信:网页登录组件(
wwlogin/sso/login):https://developer.work.weixin.qq.com/document/path/98152 - 企业微信:构建 OAuth 链接和作用域:https://developer.work.weixin.qq.com/document/path/91022
- 企业微信:获取敏感成员信息(
getuserdetail):https://developer.work.weixin.qq.com/document/path/95833 - 企业微信:读取成员(2022-06-20 字段变更):https://developer.work.weixin.qq.com/document/path/90196
- 飞书:获取 user_access_token (v1) 及字段权限:https://open.feishu.cn/document/server-docs/authentication-management/access-token/create-2
- 微信:网站应用登录:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
- 微信:UnionID:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
- Casdoor 提供商:https://github.com/casdoor/casdoor/tree/master/idp