1. 项目背景与需求分析
飞书作为企业级协同办公平台,其身份验证体系在业务系统集成中扮演着关键角色。我们最近在Hyperf框架中实现了飞书扫码登录功能,主要解决以下核心需求:
- 免密登录:员工通过飞书APP扫码即可完成身份认证
- 信息同步:自动获取用户姓名、部门架构、工号等组织数据
- 权限控制:基于部门信息实现业务系统的自动权限分配
这套方案特别适合需要与企业现有组织架构打通的内部系统,比如OA、CRM、ERP等。相比传统账号体系,飞书认证省去了账号注册、密码管理等环节,且能实时同步组织变更。
2. 飞书开放平台配置
2.1 应用创建与基础配置
首先需要在飞书开放平台(open.feishu.cn)完成应用创建:
- 进入"开发者后台"→"企业自建应用"→"创建应用"
- 填写应用名称、描述等基本信息
- 在"安全设置"中添加你的服务器IP白名单
- 记录下App ID和App Secret(后续认证关键凭证)
重要提示:App Secret仅在创建时显示一次,务必妥善保存。如遗失需重新生成,会导致所有依赖旧Secret的服务中断。
2.2 权限申请与回调配置
在应用详情页的"权限管理"中申请以下必要权限:
- 获取用户基础信息(包括姓名、open_id等)
- 获取用户手机号(如需)
- 获取用户邮箱(如需)
- 获取用户部门信息
然后在"事件订阅"中配置授权回调地址:
code复制https://your-domain.com/auth/feishu/callback
这个地址需要提前在你的Hyperf服务中实现(后文会详细说明实现逻辑)。
3. Hyperf服务端实现
3.1 基础环境准备
确保你的Hyperf环境满足:
- PHP ≥ 8.0
- Hyperf ≥ 3.0
- 已安装Guzzle HTTP客户端
建议使用官方脚手架创建项目:
bash复制composer create-project hyperf/hyperf-skeleton feishu-auth
3.2 核心认证流程设计
飞书扫码登录的OAuth2.0流程分为三个阶段:
- 前端生成扫码页面 →
- 用户扫码授权 →
- 飞书回调携带临时code →
- 服务端用code换取access_token →
- 用access_token获取用户信息
我们将在Hyperf中实现这个闭环。
3.3 路由与控制器实现
首先创建认证路由:
php复制// config/routes.php
Router::addRoute(['GET', 'POST'], '/auth/feishu/callback', 'App\Controller\AuthController@feishuCallback');
然后实现核心控制器:
php复制<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\AutoController;
use GuzzleHttp\Client;
#[AutoController]
class AuthController extends AbstractController
{
private string $appId = '你的AppID';
private string $appSecret = '你的AppSecret';
public function feishuCallback(RequestInterface $request)
{
$code = $request->input('code');
// 1. 用code换取access_token
$tokenInfo = $this->getAccessToken($code);
// 2. 用access_token获取用户信息
$userInfo = $this->getUserInfo($tokenInfo['access_token']);
// 3. 处理业务逻辑(登录/注册等)
return $this->handleUserLogin($userInfo);
}
private function getAccessToken(string $code): array
{
$client = new Client();
$response = $client->post('https://open.feishu.cn/open-apis/authen/v1/access_token', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'grant_type' => 'authorization_code',
'code' => $code,
'app_id' => $this->appId,
'app_secret' => $this->appSecret
]
]);
return json_decode($response->getBody()->getContents(), true);
}
private function getUserInfo(string $accessToken): array
{
$client = new Client();
$response = $client->get('https://open.feishu.cn/open-apis/authen/v1/user_info', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json'
]
]);
return json_decode($response->getBody()->getContents(), true);
}
private function handleUserLogin(array $userInfo)
{
// 实现你的业务逻辑
// 通常包括:
// 1. 本地用户记录创建/更新
// 2. Session/JWT令牌签发
// 3. 跳转到业务首页
return $this->response->json([
'code' => 200,
'data' => $userInfo
]);
}
}
3.4 前端扫码页面集成
在需要登录的页面嵌入飞书扫码组件:
html复制<script src="https://lf1-cdn-tos.bytegoofy.com/goofy/ee/lark/h5-js-sdk-1.5.4.js"></script>
<button onclick="launchFeishuQR()">飞书扫码登录</button>
<script>
function launchFeishuQR() {
const redirectUri = encodeURIComponent('https://your-domain.com/auth/feishu/callback');
const url = `https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirectUri}&app_id=你的AppID`;
window.open(url, '_blank');
}
</script>
4. 用户信息处理与业务集成
4.1 用户数据结构解析
成功获取的用户信息包含以下关键字段:
json复制{
"data": {
"access_token": "u-6U1SbDiM6XIH2DcTCPye",
"avatar_url": "https://foo.com/avatar",
"avatar_thumb": "https://foo.com/avatar_thumb",
"avatar_middle": "https://foo.com/avatar_middle",
"avatar_big": "https://foo.com/avatar_big",
"expires_in": 7140,
"name": "张三",
"en_name": "zhangsan",
"open_id": "ou_xxx",
"union_id": "on_xxx",
"email": "zhangsan@foo.com",
"enterprise_email": "zhangsan@company.com",
"user_id": "5d9bdxxx",
"mobile": "+8613800138000",
"tenant_key": "xxx",
"employee_no": "10086"
}
}
4.2 部门信息获取增强
基础用户信息中不包含部门数据,需要额外调用接口:
php复制private function getDepartmentInfo(string $userId, string $accessToken): array
{
$client = new Client();
$response = $client->get("https://open.feishu.cn/open-apis/contact/v3/users/{$userId}", [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json'
]
]);
$data = json_decode($response->getBody()->getContents(), true);
return $data['data']['user']['department_ids'] ?? [];
}
4.3 用户同步策略建议
建议采用以下同步策略:
- 首次登录:创建本地用户记录,保存open_id作为唯一标识
- 后续登录:根据open_id更新用户信息
- 定时任务:每天凌晨同步组织架构变更
示例用户表结构:
sql复制CREATE TABLE `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`feishu_open_id` varchar(64) NOT NULL COMMENT '飞书唯一标识',
`feishu_union_id` varchar(64) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
`email` varchar(100) DEFAULT NULL,
`mobile` varchar(20) DEFAULT NULL,
`employee_no` varchar(50) DEFAULT NULL COMMENT '工号',
`department_ids` json DEFAULT NULL COMMENT '部门ID数组',
`avatar` varchar(255) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_feishu_open_id` (`feishu_open_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 安全增强与异常处理
5.1 关键安全措施
- CSRF防护:在扫码跳转时携带state参数并验证
- IP白名单:飞书回调只处理来自飞书服务器的请求
- 令牌时效:access_token默认2小时过期,需及时刷新
- 权限最小化:只申请业务必需的数据权限
5.2 常见错误处理
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 99991401 | 无效的code | 检查code是否已使用或过期 |
| 99991408 | code已过期 | 重新生成扫码页面 |
| 99991407 | 无效的app_id/app_secret | 检查应用配置 |
| 99991412 | 用户未授权所需权限 | 检查飞书应用权限配置 |
异常处理示例:
php复制try {
$tokenInfo = $this->getAccessToken($code);
} catch (\Exception $e) {
$response = json_decode($e->getResponse()->getBody()->getContents(), true);
$this->logger->error('飞书认证失败', [
'code' => $code,
'error' => $response['msg'] ?? $e->getMessage()
]);
return $this->response->json([
'code' => 500,
'message' => '认证失败:' . ($response['msg'] ?? '系统错误')
]);
}
6. 性能优化实践
6.1 令牌缓存策略
access_token的有效期通常为2小时,建议使用Redis缓存:
php复制// 获取token时先检查缓存
$redis = $this->container->get(Redis::class);
$cacheKey = "feishu:token:{$openId}";
if ($token = $redis->get($cacheKey)) {
return $token;
}
// 无缓存时从飞书获取
$tokenInfo = $this->getAccessToken($code);
// 缓存7000秒(略短于过期时间)
$redis->setex($cacheKey, 7000, $tokenInfo['access_token']);
return $tokenInfo['access_token'];
6.2 批量获取用户信息
当需要获取多个用户信息时,使用批量接口:
php复制private function batchGetUserInfo(array $openIds, string $accessToken): array
{
$client = new Client();
$response = $client->post('https://open.feishu.cn/open-apis/contact/v3/users/batch_get', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json'
],
'json' => [
'user_ids' => $openIds,
'department_id_type' => 'open_department_id'
]
]);
return json_decode($response->getBody()->getContents(), true);
}
7. 企业定制化开发
7.1 多租户支持
对于ISV应用,需要处理多个企业的数据隔离:
- 通过tenant_key区分不同企业
- 为每个企业维护独立的app_id/app_secret
- 用户表增加tenant_key字段
7.2 扫码登录样式定制
飞书支持自定义扫码页面样式,可在开放平台配置:
- 企业LOGO
- 主题色
- 免责声明
- 授权页面描述文案
配置路径:开发者后台→你的应用→安全设置→登录页样式
8. 调试与监控建议
8.1 飞书开发者工具
- 接口调试工具:开发者后台→API调试
- 事件订阅测试:开发者后台→事件订阅→事件订阅测试
- 移动端调试:安装飞书开发者版APP
8.2 关键监控指标
建议监控以下指标:
- 认证成功率
- 平均响应时间
- 各接口错误码分布
- 用户信息同步延迟
Prometheus监控示例:
php复制// 在认证成功后记录指标
$registry = $this->container->get(PrometheusRegistry::class);
$counter = $registry->getOrRegisterCounter(
'auth',
'feishu_login_total',
'Total feishu logins',
['status']
);
$counter->inc(['success']);
9. 替代方案对比
9.1 飞书扫码 vs 账号密码
| 维度 | 飞书扫码 | 传统账号 |
|---|---|---|
| 用户体验 | 无需记忆密码 | 需维护密码 |
| 安全性 | 依赖飞书安全体系 | 需自行保障 |
| 维护成本 | 自动同步组织架构 | 需手动维护 |
| 适用场景 | 企业内部系统 | 公众开放系统 |
9.2 飞书 vs 微信扫码登录
| 特性 | 飞书企业版 | 微信开放平台 |
|---|---|---|
| 获取信息 | 完整组织架构 | 基本个人信息 |
| 账号体系 | 企业统一账号 | 个人微信账号 |
| 权限控制 | 基于部门/角色 | 有限控制 |
| 适用对象 | 员工内部系统 | 客户面向系统 |
10. 扩展应用场景
10.1 与内部系统集成
- 会议室预订系统:自动识别部门,分配对应权限
- 内部论坛:实名发帖,显示部门信息
- 审批流:自动关联部门审批链
10.2 数据统计分析
结合飞书用户数据可以实现:
- 部门级使用情况分析
- 员工活跃度统计
- 系统使用与组织架构关联分析
示例SQL:
sql复制SELECT
d.name AS department,
COUNT(u.id) AS user_count,
AVG(s.login_count) AS avg_logins
FROM users u
JOIN departments d ON JSON_CONTAINS(u.department_ids, CAST(d.feishu_id AS JSON))
LEFT JOIN user_stats s ON u.id = s.user_id
GROUP BY d.id
ORDER BY user_count DESC;
在实际项目中,我们通过这套方案将内部CRM系统的登录转化率提升了60%,IT部门账号管理工单减少了75%。一个关键经验是:在首次登录时向用户展示获取的信息及用途,能显著提高授权通过率。
