Files
gitea-jira-task-bot/larkauth.md

488 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 获取授权码
本接口用于发起用户授权,应用在用户同意授权后将获得授权码 `code`。请注意授权码的有效期为 5 分钟,且只能被使用一次。
## 注意事项
- 本接口实际为授权页面,适用于网页应用的授权场景。在需要用户授权时,应用应将用户重定向至本授权页面。当用户在授权页面点击授权后(在飞书客户端内打开网页应用时可免确认直接跳转),浏览器将跳转至 `redirect_uri` 所指定的地址,并携带 `code` 查询参数(即授权码)。
- 开发者可通过授权码获取 `user_access_token`,以调用 OpenAPI例如[获取用户信息](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/user_info/get))。有关获取 `user_access_token` 的详细步骤,可参考[获取 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)。
- 通过本接口配合[获取 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)以及[获取用户信息](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/user_info/get),应用可实现飞书授权登录。
- 完整的用户授权链路实现,可参考[浏览器应用接入指南](https://open.feishu.cn/document/common-capabilities/sso/web-application-end-user-consent/guide)。
- 在打开授权页面时,需要通过拼接 `scope` 查询参数声明应用所需的用户授权权限。例如,获取通讯录基本信息的权限对应的 `scope` 键为 `contact:contact.base:readonly`
- 用户授予应用的权限是累积的,最新生成的 `user_access_token` 将包含用户历史上已授予的所有权限。
- 当应用使用 `user_access_token` 调用某个 OpenAPI 时,必须确保该 `user_access_token` 具备[目标 OpenAPI 所需的权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN),否则调用将失败。
## 请求
基本 |  
---|---
HTTP URL | https://accounts.feishu.cn/open-apis/authen/v1/authorize
HTTP Method | GET
接口频率限制 | 1000 次/分钟、50 次/秒
支持的应用类型 | Custom App、Store App
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用** | 无
### 查询参数
> 为了确保 URL 构造 & 编码正确,建议使用相关的 URL 标准库来完成 URL 的解析和构建,避免手动拼接。
名称 | 类型 | 必填 | 描述
---|---|---|---
client_id | string | 是 | 应用的 App ID可以在开发者后台的**凭证与基础信息**页面查看 App ID。有关 App ID 的详细介绍,请参考[通用参数](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/terminology)。<br>**示例值:** `cli_a5d611352af9d00b`
response_type | string | 是 | 应用通知授权服务器所需的授权类型,对于授权码流程,固定值`code` <br>**示例值:** `code`
redirect_uri | string | 是 | 应用重定向地址,在用户授权成功后会跳转至该地址,同时会携带 `code` 以及 `state` 参数(如有传递 `state` 参数)。<br>请注意: <br>1. 该地址需经过 URL 编码;<br>2. 调用本接口前,你需要在开发者后台应用的**安全设置**页面,将用于接受 OAuth 回调的 HTTP GET 请求接口地址配置为应用的重定向 URL。重定向 URL 支持配置多个,只有在重定向 URL 列表中的 URL 才会通过开放平台的安全校验。详情请参考[配置重定向域名](https://open.feishu.cn/document/uYjL24iN/uYjN3QjL2YzN04iN2cDN)。<br>**示例值:** `https://example.com/api/oauth/callback`
scope | string | 否 | 用户需要增量授予应用的权限。<br>**格式要求:** `scope` 参数为空格分隔,区分大小写的字符串。<br>**注意**<br>- 开发者需要根据业务场景,在[开发者后台](https://open.larkoffice.com/app)的 **权限管理** 模块中完成调用 OpenAPI 所需的 `scope` 申请后,自主拼接 `scope` 参数。如果没有在应用后台为应用申请相应权限,则实际使用应用时用户会遇到 20027 报错。<br>- 应用最多一次可以请求用户授予 50 个 `scope`。详情参考 [API 权限列表](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。<br>- 如果后续需要获取 `refresh_token`,此处需要添加 `offline_access` 权限。详情参考 [刷新 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/refresh-user-access-token)<br>offline_access(offline_access) <br>**示例值:** `contact:contact bitable:app:readonly`
state | string | 否 | 用来维护请求和回调之间状态的附加字符串,在授权完成回调时会原样回传此参数。应用可以根据此字符串来判断上下文关系,同时该参数也可以用以防止 CSRF 攻击,请务必校验 `state` 参数前后是否一致。<br>**示例值:** `RANDOMSTRING`
code_challenge | string | 否 | 用于通过 PKCEProof Key for Code Exchange流程增强授权码的安全性。<br>**示例值:** `E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM`<br>有关 PKCE 的详细信息,请参阅[RFC-7636 - Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636)。
code_challenge_method | string | 否 | 生成 `code_challenge` 的方法。<br>**可选值** <br>1. **`S256`**(推荐): <br>使用 SHA-256 哈希算法计算 `code_verifier` 的哈希值,并将结果进行 Base64URL 编码,生成 `code_challenge`。 <br>2. **`plain`**(默认值): <br>直接将 `code_verifier` 作为 `code_challenge`,无需进行额外处理。<br>以上 `code_verifier` 是指在发起授权前,本地生成的随机字符串。
### 请求示例
> 注意仅为示例请求 URL请根据前文描述将其中的查询参数替换为真实的值
``` http
https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=cli_a5d611352af9d00b&response_type=code&redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Foauth%2Fcallback&scope=bitable:app:readonly%20contact:contact&state=RANDOMSTRING
```
## 响应
### 成功响应
当用户同意授权后,浏览器将重定向至发起授权时给定的的 `redirect_uri` 地址,同时携带 `code` 和 `state` 参数。
名称 | 描述
---|---
code | 授权码,用于获取 `user_access_token`。<br>**字符集:** [A-Z] / [a-z] / [0-9] / "-" / "_"<br>**长度:** 请开发者至少预留 64 位字符<br>**示例值:** `2Wd5g337vo5BZXUz-3W5KECsWUmIzJ_FJ1eFD59fD1AJIibIZljTu3OLK-HP_UI1`
state | 打开授权页时传入的 `state` 参数的原值,如未传入此处不会返回。
示例:
```http
https://example.com/api/oauth/callback?code=2Wd5g337vo5BZXUz-3W5KECsWUmIzJ_FJ1eFD59fD1AJIibIZljTu3OLK-HP_UI1&state=RANDOMSTRING
```
### 失败响应
当用户拒绝授权时,浏览器将重定向至发起授权时给定的 `redirect_uri` 地址,同时携带 `error` 和 `state` 查询参数。 当前 `error` 参数的固定值为 `access_denied`,请妥善处理拒绝授权时的情况。
名称 | 描述
---|---
error | 错误信息,当前固定为 `access_denied`
state | 打开授权页时传入的 `state` 参数的原值,如未传入此处不会返回
示例:
```http
https://example.com/api/oauth/callback?error=access_denied&state=RANDOMSTRING
```
## 常见问题
### 用户授权应用时报错 20027
**问题现象**:用户在授权应用时报错 20027
![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/8de8a8e0ce3fbe84ddabcbad2e929b5c_cUP75TqvF9.png?height=331&lazyload=true&maxWidth=600&width=792)
**问题原因**:打开授权页时拼接的 scope 参数中包含当前应用未开通的权限。
**解决方案**
1. 确认需要用户授权的权限范围。
2. 前往[开发者后台](https://open.feishu.cn/app),在对应应用的 **开发配置** > **权限管理** > **API 权限** 功能页开通相应的权限。具体操作参考[申请 API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。
3. 调用当前接口,自主拼接已在应用内开通的权限。
### 如何获取包含目标权限的 user_access_token
在调用 OpenAPI 时,如果 `user_access_token` 缺少所需权限,将会返回以下错误:
```json
{
"code": 99991679,
"error": {
"log_id": "202407260711088FB107A76E0100002087",
"permission_violations": [
{
"subject": "task:task:read",
"type": "action_privilege_required"
},
{
"subject": "task:task:write",
"type": "action_privilege_required"
}
]
},
"msg": "Unauthorized. You do not have permission to perform the requested operation on the resource. Please request user re-authorization and try again. required one of these privileges: [task:task:read, task:task:write]"
}
```
为避免因 `user_access_token` 权限不足导致 OpenAPI 调用失败,开发者可通过 `scope` 查询参数请求用户授予相应权限,具体有以下两种方式:
1. 一次性拼接所有需要用户授权的 `scope`,在无新增权限需求的情况下,无需重复授权。需注意单次拼接的 `scope` 数量上限为 50 个。
2. 或者,根据 OpenAPI 调用返回的错误码及 `permission_violations` 字段,识别当前操作所需的额外权限。随后可重新生成授权链接,提示用户补充授权,并使用新生成的 `user_access_token` 继续调用 OpenAPI。
建议开发者遵循最小权限原则,仅要求用户授予必要的权限。
### redirect_uri 中带 # 时的说明
标准 [RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax](https://datatracker.ietf.org/doc/html/rfc3986#section-3)中约定URI 中 `#` 后面的内容称为 fragment位置处于 URI 最后。如果业务授权请求参数 `redirect_uri` 拼接了 `#`,授权完成后的重定向会将 `#` 和 fragment 内容拼接到 URI 最后。业务在解析获取 `code` 时需要特别注意。
`redirect_uri` 示例:
```
https://example.com/api/oauth/callback/#/login
```
请求示例:
```
GET https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=cli_a5d611352af9d00b&redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Foauth%2Fcallback%2F%23%2Flogin%0A&scope=bitable:app:readonly%20contact:contact&state=RANDOMSTRING
```
回调后浏览器地址栏中的值示例:
```shell
https://example.com/api/oauth/callback?code=2Wd5g337vo5BZXUz-3W5KECsWUmIzJ_FJ1eFD59fD1AJIibIZljTu3OLK-HP_UI1&state=RANDOMSTRING#/login
```
# 获取 user_access_token
OAuth 令牌接口,可用于获取 <code>user_access_token</code> 以及 <code>refresh_token</code>。<code>user_access_token</code> 为用户访问凭证,使用该凭证可以以用户身份调用 OpenAPI。<code>refresh_token</code> 为刷新凭证,可以用来获取新的 <code>user_access_token</code>。
- 获取 `user_access_token` 前需要先获取授权码,详见[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)。请注意授权码的有效期为 5 分钟,且只能被使用一次。
- 用户授权时,用户必须拥有[应用的使用权限](https://open.feishu.cn/document/home/introduction-to-scope-and-authorization/availability),否则调用本接口将会报错误码 20010。
- 获取到的 `user_access_token` 存在有效期,如何刷新 <code>user_access_token</code> 详见[刷新 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/refresh-user-access-token)。
- 如果你需要获取用户信息,详见[获取用户信息](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/user_info/get)。
**注意事项**:本接口实现遵循 [RFC 6749 - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) ,你可以使用[标准的 OAuth 客户端库](https://oauth.net/code/)进行接入(**推荐**
## 请求
基本 | &nbsp;
---|---
HTTP URL | https://open.feishu.cn/open-apis/authen/v2/oauth/token
HTTP Method | POST
接口频率限制 | [1000 次/分钟、50 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN)
支持的应用类型 | Custom App、Store App
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用<br>** | 无
字段权限要求 | `refresh_token` 以及 `refresh_token_expires_in` 字段仅在具备以下权限时返回:<br>offline_access(offline_access)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Content-Type | string | 是 | 请求体类型。<br>**固定值:**`application/json; charset=utf-8`
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
grant_type | string | 是 | 授权类型。<br>**固定值:**`authorization_code`
client_id | string | 是 | 应用的 App ID。应用凭证 App ID 和 App Secret 获取方式:<br>1. 登录[飞书开发者后台](https://open.feishu.cn/app)。<br>2. 进入应用详情页,在左侧导航栏,单击 **凭证与基础信息**。<br>3. 在 **应用凭证** 区域,获取并保存 **App ID** 和 **App Secret**。<br>**示例值:**`cli_a5ca35a685b0x26e`
client_secret | string | 是 | 应用的 App Secret。应用凭证 App ID 和 App Secret 获取方式:<br>1. 登录[飞书开发者后台](https://open.feishu.cn/app)。<br>2. 进入应用详情页,在左侧导航栏,单击 **凭证与基础信息**。<br>3. 在 **应用凭证** 区域,获取并保存 **App ID** 和 **App Secret**。<br>**示例值:**`baBqE5um9LbFGDy3X7LcfxQX1sqpXlwy`
code | string | 是 | 授权码,详见[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)。<br>**示例值:**`a61hb967bd094dge949h79bbexd16dfe`
redirect_uri | string | 否 | 在构造授权页页面链接时所拼接的应用回调地址。<br>**示例值:**`https://example.com/api/oauth/callback`
code_verifier | string | 否 | 在发起授权前,本地生成的随机字符串,用于 PKCEProof Key for Code Exchange流程。使用 PKCE 时,该值为必填项。 <br>有关 PKCE 的详细介绍,请参阅 [RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636)。<br>**长度限制:** 最短 43 字符,最长 128 字符<br>**可用字符集:** [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"<br>**示例值:**`TxYmzM4PHLBlqm5NtnCmwxMH8mFlRWl_ipie3O0aVzo`
scope | string | 否 | 该参数用于缩减 `user_access_token` 的权限范围。<br>例如:<br>1. 在[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时通过 `scope` 参数授权了 `contact:user.base:readonly contact:contact.base:readonly contact:user.employee:readonly` 三个权限。<br>2. 在当前接口可通过 `scope` 参数传入 `contact:user.base:readonly`,将 `user_access_token` 的权限缩减为 `contact:user.base:readonly` 这一个。<br>**注意**<br>- 如果不指定当前参数,生成的 `user_access_token` 将包含用户授权时的所有权限。<br>- 当前参数不能传入重复的权限,否则会接口调用会报错,返回错误码 20067。<br>- 当前参数不能传入未授权的权限(即[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时用户已授权范围外的其他权限),否则接口调用会报错,返回错误码 20068。<br>- 多次调用当前接口缩减权限的范围不会叠加。例如,用户授予了权限 A 和 B第一次调用该接口缩减为权限 A则 `user_access_token` 只包含权限 A第二次调用该接口缩减为权限 B则 `user_access_token` 只包含权限 B。 <br>- 生效的权限列表可通过本接口返回值 scope 查看。<br>**格式要求:** 以空格分隔的 `scope` 列表<br>**示例值:**`auth:user.id:read task:task:read`
### 请求体示例
```json
{
"grant_type": "authorization_code",
"client_id": "cli_a5ca35a685b0x26e",
"client_secret": "baBqE5um9LbFGDy3X7LcfxQX1sqpXlwy",
"code": "a61hb967bd094dge949h79bbexd16dfe",
"redirect_uri": "https://example.com/api/oauth/callback",
"code_verifier": "TxYmzM4PHLBlqm5NtnCmwxMH8mFlRWl_ipie3O0aVzo"
}
```
## 响应
响应体类型为 `application/json; charset=utf-8`。
### 响应体
**注意事项****响应体中的 `access_token` 和 `refresh_token` 长度较长**,一般在 1~2KB 之间,且可能由于 `scope` 数量的变多或后续变更导致长度进一步增加,建议预留 4KB 的存储容量
名称 | 类型 | 描述
---|---|---
code | int | 错误码,为 0 时表明请求成功,非 0 表示失败,请参照下文[错误码](#错误码)一节进行相应处理
access_token | string | 即 `user_access_token`,仅在请求成功时返回
expires_in | int | 即 `user_access_token` 的有效期,单位为秒,仅在请求成功时返回<br>**注意事项**:建议使用该字段以确定 `user_access_token` 的过期时间,不要硬编码有效期
refresh_token | string | 用于刷新 `user_access_token`,详见[刷新 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/refresh-user-access-token)。该字段仅在请求成功且用户授予 `offline_access` 权限时返回。<br>**注意事项**:如果你在获取 `user_access_token` 时设置了 `scope` 请求参数,且需要返回 `refresh_token`,则需要在 `scope` 参数中包括 `offline_access`。另外,`refresh_token` 仅能被使用一次。
refresh_token_expires_in | int | 即 `refresh_token` 的有效期,单位为秒,仅在返回 `refresh_token` 时返回。<br>**注意事项**:建议在到期前调用[刷新 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/refresh-user-access-token) 接口获取新的 `refresh_token`。
token_type | string | 值固定为 `Bearer`,仅在请求成功时返回
scope | string | 本次请求所获得的 `access_token` 所具备的权限列表,以空格分隔,仅在请求成功时返回
error | string | 错误类型,仅在请求失败时返回
error_description | string | 具体的错误信息,仅在请求失败时返回
### 响应体示例
成功响应示例:
```json
{
"code": 0,
"access_token": "eyJhbGciOiJFUzI1NiIs**********X6wrZHYKDxJkWwhdkrYg",
"expires_in": 7200, // 非固定值,请务必根据响应体中返回的实际值来确定 access_token 的有效期
"refresh_token": "eyJhbGciOiJFUzI1NiIs**********XXOYOZz1mfgIYHwM8ZJA",
"refresh_token_expires_in": 604800, // 非固定值,请务必根据响应体中返回的实际值来确定 refresh_token 的有效期
"scope": "auth:user.id:read offline_access task:task:read user_profile",
"token_type": "Bearer"
}
```
失败响应示例:
```json
{
"code": 20050,
"error": "server_error",
"error_description": "An unexpected server error occurred. Please retry your request."
}
```
### 错误码
HTTP 状态码 | 错误码 | 描述 | 排查建议
---|---|---|---
400 | 20001 | The request is missing a required parameter. | 必要参数缺失,请检查请求时传入的参数是否有误
400 | 20002 | The client secret is invalid. | 应用认证失败,请检查提供的 `client_id` 与 `client_secret` 是否正确
400 | 20003 | The authorization code is not found. Please note that an authorization code can only be used once. | 无效的授权码,请检查授权码是否有效,注意授权码仅能使用一次
400 | 20004 | The authorization code has expired. | 授权码已经过期,请在授权码生成后的 5 分钟内使用
400 | 20008 | The user does not exist. | 用户不存在,请检查发起授权的用户的当前状态
400 | 20009 | The specified app is not installed. | 租户未安装应用,请检查应用状态
400 | 20010 | The user does not have permission to use this app. | 用户无应用使用权限,请检查发起授权的用户是否仍具有应用使用权限
400 | 20024 | The provided authorization code or refresh token does not match the provided client ID. | 提供的授权码与 `client_id` 不匹配,请勿混用不同应用的凭证
400 | 20036 | The specified grant_type is not supported. | 无效的 `grant_type`,请检查请求体中 `grant_type` 字段的取值
400 | 20048 | The specified app does not exist. | 应用不存在,请检查应用状态
400 | 20049 | PKCE code challenge failed. | PKCE 校验失败,请检查请求体中 `code_verifier` 字段是否存在且有效
500 | 20050 | An unexpected server error occurred. Please retry your request. | 内部服务错误,请稍后重试,如果持续报错请联系[技术支持](https://applink.feishu.cn/TLJpeNdW)
400 | 20063 | The request is malformed. Please check your request. | 请求体中缺少必要字段,请根据具体的错误信息补齐字段
400 | 20065 | The authorization code has been used. Please note that an authorization code can only be used once. | 授权码已被使用,授权码仅能使用一次,请检查是否有被重复使用
400 | 20066 | The user status is invalid. | 用户状态非法,请检查发起授权的用户的当前状态
400 | 20067 | The provided scope list contains duplicate scopes. Please ensure all scopes are unique. | 无效的 `scope` 列表,其中存在重复项,请确保传入的 `scope` 列表中没有重复项
400 | 20068 | The provided scope list contains scopes that are not permitted. Please ensure all scopes are allowed. | 无效的 `scope` 列表,其中存在用户未授权的权限。当前接口 `scope` 参数传入的权限必须是[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时 `scope` 参数值的子集。<br>例如,在获取授权码时,用户授权了权限 A、B则当前接口 `scope` 可传入的值只有权限 A、B若传入权限 C 则会返回当前错误码。
400 | 20069 | The specified app is not enabled. | 应用未启用,请检查应用状态
400 | 20070 | Multiple authentication methods were provided. Please only use one to proceed. | 请求时同时使用了 `Basic Authentication` 和 `client_secret` 两种身份验证方式。请仅使用 `client_id`、`client_secret` 身份验证方式调用本接口。
400 | 20071 | The provided redirect URI does not match the one used during authorization. | 无效的 `redirect_uri`,请确保 `redirect_uri` 与[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时传入的 `redirect_uri` 保持一致
503 | 20072 | The server is temporarily unavailable. Please retry your request. | 服务暂不可用,请稍后重试
## 代码示例
**注意事项**:此处提供的代码示例**仅供参考**,请勿直接在生产环境使用
### Golang
运行下面示例程序的步骤:
1. 点击下方代码块右上角复制按钮,将代码复制到本地文件中,保存为 `main.go`
2. 参照注释部分,完成配置;
3. 在 `main.go` 所在目录下新建 `.env` 文件,内容如下:
```bash
APP_ID=cli_xxxxxx # 仅为示例值,请使用你的应用的 App ID获取方式开发者后台 -> 基础信息 -> 凭证与基础信息 -> 应用凭证 -> App ID
APP_SECRET=xxxxxx # 仅为示例值,请使用你的应用的 App Secret获取方式开发者后台 -> 基础信息 -> 凭证与基础信息 -> 应用凭证 -> App Secret
```
4. 在 `main.go` 所在目录执行以下命令:
```bash
go mod init oauth-test
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/sessions
go get github.com/gin-contrib/sessions/cookie
go get github.com/joho/godotenv
go get golang.org/x/oauth2
go run main.go
```
5. 浏览器打开 [http://localhost:8080](http://localhost:8080) ,按照页面提示完成授权流程;
```javascript
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
_ "github.com/joho/godotenv/autoload"
"golang.org/x/oauth2"
)
var oauthEndpoint = oauth2.Endpoint{
AuthURL: "https://accounts.feishu.cn/open-apis/authen/v1/authorize",
TokenURL: "https://open.feishu.cn/open-apis/authen/v2/oauth/token",
}
var oauthConfig = &oauth2.Config{
ClientID: os.Getenv("APP_ID"),
ClientSecret: os.Getenv("APP_SECRET"),
RedirectURL: "http://localhost:8080/callback", // 请先添加该重定向 URL配置路径开发者后台 -> 开发配置 -> 安全设置 -> 重定向 URL -> 添加
Endpoint: oauthEndpoint,
Scopes: []string{"offline_access"}, // 如果你不需要 refresh_token请注释掉该行否则你需要先申请 offline_access 权限方可使用,配置路径:开发者后台 -> 开发配置 -> 权限管理
}
func main() {
r := gin.Default()
// 使用 Cookie 存储 session
store := cookie.NewStore([]byte("secret")) // 此处仅为示例,务必不要硬编码密钥
r.Use(sessions.Sessions("mysession", store))
r.GET("/", indexController)
r.GET("/login", loginController)
r.GET("/callback", oauthCallbackController)
fmt.Println("Server running on http://localhost:8080")
log.Fatal(r.Run(":8080"))
}
func indexController(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
var username string
session := sessions.Default(c)
if session.Get("user") != nil {
username = session.Get("user").(string)
}
html := fmt.Sprintf(`<html><head><style>body{font-family:Arial,sans-serif;background:#f4f4f4;margin:0;display:flex;justify-content:center;align-items:center;height:100vh}.container{text-align:center;background:#fff;padding:30px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,0.1)}a{padding:10px 20px;font-size:16px;color:#fff;background:#007bff;border-radius:5px;text-decoration:none;transition:0.3s}a:hover{background:#0056b3}}</style></head><body>[返回主页](/)
</body></html>`, user.Data.Name)
c.String(http.StatusOK, html)
}
```
# 刷新 user_access_token
OAuth 令牌接口,可用于刷新 <code>user_access_token</code> 以及获取新的 <code>refresh_token</code>。
- `user_access_token` 为用户访问凭证,使用该凭证可以以用户身份调用 OpenAPI该凭证存在有效期可通过 `refresh_token` 进行刷新。
- 用户授权时,用户必须拥有[应用的使用权限](https://open.feishu.cn/document/home/introduction-to-scope-and-authorization/availability),否则调用本接口将会报错误码 20010。
- `refresh_token` 用于获取新的 `user_access_token`,且仅能使用一次。在获取新的 `user_access_token` 时会返回新的 `refresh_token`,原 `refresh_token` 立即失效。
- 首次获取 `refresh_token` 的方式参见[获取 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)。
**注意事项**:本接口实现遵循 [RFC 6749 - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) ,你可以使用[标准的 OAuth 客户端库](https://oauth.net/code/)进行接入(**推荐**
## 前置工作
### 开通 offline_access 权限
获取 `refresh_token` 需前往开放平台应用后台的**权限管理**模块开通 `offline_access` 权限,并在[发起授权](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时在 `scope` 参数中声明该权限。
![开通 offline_access 权限.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/f8b75edae2c682ab98b6984170707a64_gYt5eNq84a.png?height=703&lazyload=true&width=1867)
在开通 `offline_access` 权限后,如需获取 `refresh_token`,具体的请求参数设置如下:
1. 首先在[发起授权](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时,授权链接的`scope` 参数中必须拼接 `offline_access`,例如:
```
https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=cli_a5d611352af9d00b&redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Foauth%2Fcallback&scope=bitable:app:readonly%20offline_access
```
2. 在[获取 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)时,
+ 如果不需要缩减权限,即该接口的 `scope` 参数为空,则无需做其他操作,即可正常获得 `refresh_token`
+ 如果需要缩减权限,即该接口的 `scope` 参数不为空,
+ 且需要获取 `refresh_token`,则此处的 `scope` 参数中需要拼接 `offline_access`
+ 如不需要获取 `refresh_token`,则无需特殊处理;
3. 在[刷新 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/refresh-user-access-token)时,同第二步的逻辑。
### 开启刷新 user_access_token 的安全设置
**注意事项**- 如果你看不到此开关则无需关注,其默认处于开启状态。
- 完成配置后需要发布应用使配置生效。具体操作参见[发布企业自建应用](https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process#baf09c7d)、[发布商店应用](https://open.feishu.cn/document/uMzNwEjLzcDMx4yM3ATM/uYjMyUjL2IjM14iNyITN)。
前往开放平台应用后台的**安全设置**模块,打开刷新 `user_access_token` 的开关。
![开启刷新 user_access_token 的安全设置.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/194824525c33e70cd796579744571c2d_WbBjYQW3zR.png?height=796&lazyload=true&width=1907)
## 请求
**注意事项**:为了避免刷新 `user_access_token` 的行为被滥用,在用户授权应用 365 天后,应用必须通过用户[重新授权](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)的方式来获取 `user_access_token` 与 `refresh_token`。如果 `refresh_token` 到期后继续刷新`user_access_token`将报错(错误码为 20037可参考以下[错误码描述信息](#错误码)进行处理。
**注意事项**:刷新后请更新本地 `user_access_token` 和 `refresh_token`,原令牌将无法再使用(`user_access_token` 会有一分钟的豁免时间以供应用完成令牌轮转)。
基本 | &nbsp;
---|---
HTTP URL | https://open.feishu.cn/open-apis/authen/v2/oauth/token
HTTP Method | POST
接口频率限制 | [1000 次/分钟、50 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN)
支持的应用类型 | Custom App、Store App
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用<br>** | 无
字段权限要求 | `refresh_token` 以及 `refresh_token_expires_in` 字段仅在具备以下权限时返回:<br>offline_access(offline_access)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Content-Type | string | 是 | 请求体类型。<br>**固定值:**`application/json; charset=utf-8`
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
grant_type | string | 是 | 授权类型。<br>**固定值:**`refresh_token`
client_id | string | 是 | 应用的 App ID可以在开发者后台中的应用详情页面找到该值。<br>**示例值:**`cli_a5ca35a685b0x26e`
client_secret | string | 是 | 应用的 App Secret可以在开发者后台中的应用详情页面找到该值详见[如何获取应用的 App ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-app-id)。<br>**示例值:**`baBqE5um9LbFGDy3X7LcfxQX1sqpXlwy`
refresh_token | string | 是 | 刷新令牌,用于刷新 `user_access_token` 以及 `refresh_token`。<br>**注意事项**:请务必注意本接口仅支持[获取 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)和[刷新 user_access_token](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/refresh-user-access-token)接口返回的 `refresh_token`<br>**示例值:**`eyJhbGciOiJFUzI1NiIs**********XXOYOZz1mfgIYHwM8ZJA`
scope | string | 否 | 该参数用于缩减 `user_access_token` 的权限范围。<br>例如:<br>1. 在[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时通过 `scope` 参数授权了 `contact:user.base:readonly contact:contact.base:readonly contact:user.employee:readonly` 三个权限。<br>2. 在当前接口可通过 `scope` 参数传入 `contact:user.base:readonly`,将 `user_access_token` 的权限缩减为 `contact:user.base:readonly` 这一个。<br>**注意**<br>- 如果不指定当前参数,生成的 `user_access_token` 将包含用户授权时的所有权限。<br>- 当前参数不能传入重复的权限,否则会接口调用会报错,返回错误码 20067。<br>- 当前参数不能传入未授权的权限(即[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时用户已授权范围外的其他权限),否则接口调用会报错,返回错误码 20068。<br>- 多次调用当前接口缩减权限的范围不会叠加。例如,用户授予了权限 A 和 B第一次调用该接口缩减为权限 A则 `user_access_token` 只包含权限 A第二次调用该接口缩减为权限 B则 `user_access_token` 只包含权限 B。 <br>- 生效的权限列表可通过本接口返回值 scope 查看。<br>**格式要求:** 以空格分隔的 `scope` 列表<br>**示例值:**`auth:user.id:read task:task:read`
### 请求体示例
```json
{
"grant_type": "refresh_token",
"client_id": "cli_a5ca35a685b0x26e",
"client_secret": "baBqE5um9LbFGDy3X7LcfxQX1sqpXlwy",
"refresh_token": "eyJhbGciOiJFUzI1NiIs**********XXOYOZz1mfgIYHwM8ZJA"
}
```
## 响应
响应体类型为 `application/json; charset=utf-8`。
### 响应体
名称 | 类型 | 描述
---|---|---
code | int | 错误码,为 0 时表明请求成功,非 0 表示失败,请参照下文错误码一节妥善处理
access_token | string | 即 `user_access_token`,仅在请求成功时返回
expires_in | int | 即 `user_access_token` 的有效期,单位为秒,仅在请求成功时返回<br>**注意事项**:建议使用该字段以确定 `user_access_token` 的过期时间,不要硬编码有效期
refresh_token | string | 用于刷新 `user_access_token`,该字段仅在请求成功且用户授予 `offline_access` 权限时返回:<br>offline_access(offline_access)<br>**注意事项**:如果你在获取 `user_access_token` 时设置了 `scope` 请求参数,且需要返回 `refresh_token`,则需要在 `scope` 参数中包括 `offline_access`。另外,`refresh_token` 仅能被使用一次。
refresh_token_expires_in | int | 即 `refresh_token` 的有效期,单位为秒,仅在返回 `refresh_token` 时返回。<br>**注意事项**:建议在到期前重新调用当前接口获取新的 `refresh_token`。
token_type | string | 值固定为 `Bearer`,仅在请求成功时返回
scope | string | 本次请求所获得的 `access_token` 所具备的权限列表,以空格分隔,仅在请求成功时返回
error | string | 错误类型,仅在请求失败时返回
error_description | string | 具体的错误信息,仅在请求失败时返回
### 响应体示例
成功响应示例:
```json
{
"code": 0,
"access_token": "eyJhbGciOiJFUzI1NiIs**********X6wrZHYKDxJkWwhdkrYg",
"expires_in": 7200, // 非固定值,请务必根据响应体中返回的实际值来确定 access_token 的有效期
"refresh_token": "eyJhbGciOiJFUzI1NiIs**********VXOYOZYZmfgIYHWM0ZJA",
"refresh_token_expires_in": 604800, // 非固定值,请务必根据响应体中返回的实际值来确定 refresh_token 的有效期
"scope": "auth:user.id:read offline_access task:task:read user_profile",
"token_type": "Bearer"
}
```
失败响应示例:
```json
{
"code": 20050,
"error": "server_error",
"error_description": "An unexpected server error occurred. Please retry your request."
}
```
### 错误码
HTTP 状态码 | 错误码 | 描述 | 排查建议
---|---|---|---
400 | 20001 | The request is missing a required parameter. | 必要参数缺失,请检查请求时传入的参数是否有误
400 | 20002 | The client secret is invalid. | 应用认证失败,请检查提供的 `client_id` 与 `client_secret` 是否正确。获取方式参见 [如何获取应用的 App ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-app-id)。
400 | 20008 | The user does not exist. | 用户不存在,请检查发起授权的用户的当前状态
400 | 20009 | The specified app is not installed. | 租户未安装应用,请检查应用状态
400 | 20010 | The user does not have permission to use this app. | 用户无应用使用权限,请检查发起授权的用户是否仍具有应用使用权限
400 | 20024 | The provided authorization code or refresh token does not match the provided client ID. | 提供的 `refresh_token` 与 `client_id` 不匹配,请勿混用不同应用的凭证
400 | 20026 | The refresh token passed is invalid. Please check the value. | 请检查请求体中 `refresh_token` 字段的取值<br>请注意本接口仅支持 v2 版本接口下发的 `refresh_token`
400 | 20036 | The specified grant_type is not supported. | 无效的 `grant_type`,请检查请求体中 `grant_type` 字段的取值
400 | 20037 | The refresh token passed has expired. Please generate a new one. | `refresh_token` 已过期,请[重新发起授权流程](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)以获取新的 `refresh_token`
400 | 20048 | The specified app does not exist. | 应用不存在,请检查应用状态
500 | 20050 | An unexpected server error occurred. Please retry your request. | 内部服务错误,请稍后重试,如果持续报错请联系[技术支持](https://applink.feishu.cn/TLJpeNdW)
400 | 20063 | The request is malformed. Please check your request. | 请求体中缺少必要字段,请根据具体的错误信息补齐字段
400 | 20064 | The refresh token has been revoked. Please note that a refresh token can only be used once. | `refresh_token` 已被撤销,请[重新发起授权流程](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)以获取新的 `refresh_token`
400 | 20066 | The user status is invalid. | 用户状态非法,请检查发起授权的用户的当前状态
400 | 20067 | The provided scope list contains duplicate scopes. Please ensure all scopes are unique. | 无效的 `scope` 列表,其中存在重复项,请确保传入的 `scope` 列表中没有重复项
400 | 20068 | The provided scope list contains scopes that are not permitted. Please ensure all scopes are allowed. | 无效的 `scope` 列表,其中存在用户未授权的权限。当前接口 `scope` 参数传入的权限必须是[获取授权码](https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code)时 `scope` 参数值的子集。<br>例如,在获取授权码时,用户授权了权限 A、B则当前接口 `scope` 可传入的值只有权限 A、B若传入权限 C 则会返回当前错误码。
400 | 20069 | The specified app is not enabled. | 应用未启用,请检查应用状态
400 | 20070 | Multiple authentication methods were provided. Please only use one to proceed. | 请求时同时使用了 `Basic Authentication` 和 `client_secret` 两种身份验证方式。请仅使用 `client_id`、`client_secret` 身份验证方式调用本接口。
503 | 20072 | The server is temporarily unavailable. Please retry your request. | 服务暂不可用,请稍后重试
400 | 20073 | The refresh token has been used. Please note that a refresh token can only be used once. | 请[重新发起授权流程](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/authentication-management/access-token/get-user-access-token)以获取新的 `refresh_token`
400 | 20074 | The specified app is not allowed to refresh token. | 请在应用管理后台检查是否开启了刷新 `user_access_token` 开关,注意发版后生效