飞书 + Lua 实现企业级组织架构登录认证

简介: 飞书是字节跳动旗下一款企业级协同办公软件,本文将介绍如何基于飞书开放平台的身份验证能力,使用 Lua 实现企业级组织架构的登录认证网关。

飞书是字节跳动旗下一款企业级协同办公软件,本文将介绍如何基于飞书开放平台的身份验证能力,使用 Lua 实现企业级组织架构的登录认证网关。

登录流程

让我们首先看一下飞书第三方网站免登的整体流程:

第一步: 网页后端发现用户未登录,请求身份验证;
第二步: 用户登录后,开放平台生成登录预授权码,302跳转至重定向地址;
第三步: 网页后端调用获取登录用户身份校验登录预授权码合法性,获取到用户身份;
第四步: 如需其他用户信息,网页后端可调用获取用户信息(身份验证)。

浏览器内网页登录

Lua 实现

飞书接口部分实现

获取应用的 access_token

function _M:get_app_access_token()
    local url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
    local body = {
        app_id = self.app_id,
        app_secret = self.app_secret
    }
    local res, err = http_post(url, body, nil)
    if not res then
        return nil, err
    end
    if res.status ~= 200 then
        return nil, res.body
    end
    local data = json.decode(res.body)
    if data["code"] ~= 0 then
        return nil, res.body
    end
    return data["tenant_access_token"]
end

通过回调 code 获取登录用户信息

function _M:get_login_user(code)
    local app_access_token, err = self:get_app_access_token()
    if not app_access_token then
        return nil, "get app_access_token failed: " .. err
    end
    local url = "https://open.feishu.cn/open-apis/authen/v1/access_token"
    local headers = {
        Authorization = "Bearer " .. app_access_token
    }
    local body = {
        grant_type = "authorization_code",
        code = code
    }
    ngx.log(ngx.ERR, json.encode(body))
    local res, err = http_post(url, body, headers)
    if not res then
        return nil, err
    end
    local data = json.decode(res.body)
    if data["code"] ~= 0 then
        return nil, res.body
    end
    return data["data"]
end

获取用户详细信息

获取登录用户信息时无法获取到用户的部门信息,故这里需要使用登录用户信息中的 open_id 获取用户的详细信息,同时 user_access_token 也是来自于获取到的登录用户信息。

function _M:get_user(user_access_token, open_id)
    local url = "https://open.feishu.cn/open-apis/contact/v3/users/" .. open_id
    local headers = {
        Authorization = "Bearer " .. user_access_token
    }
    local res, err = http_get(url, nil, headers)
    if not res then
        return nil, err
    end
    local data = json.decode(res.body)
    if data["code"] ~= 0 then
        return nil, res.body
    end
    return data["data"]["user"], nil
end

登录信息

JWT 登录凭证

我们使用 JWT 作为登录凭证,同时用于保存用户的 open_iddepartment_ids

-- 生成 token
function _M:sign_token(user)
    local open_id = user["open_id"]
    if not open_id or open_id == "" then
        return nil, "invalid open_id"
    end
    local department_ids = user["department_ids"]
    if not department_ids or type(department_ids) ~= "table" then
        return nil, "invalid department_ids"
    end

    return jwt:sign(
        self.jwt_secret,
        {
            header = {
                typ = "JWT",
                alg = jwt_header_alg,
                exp = ngx.time() + self.jwt_expire
            },
            payload = {
                open_id = open_id,
                department_ids = json.encode(department_ids)
            }
        }
    )
end

-- 验证与解析 token
function _M:verify_token()
    local token = ngx.var.cookie_feishu_auth_token
    if not token then
        return nil, "token not found"
    end

    local result = jwt:verify(self.jwt_secret, token)
    ngx.log(ngx.ERR, "jwt_obj: ", json.encode(result))
    if result["valid"] then
        local payload = result["payload"]
        if payload["department_ids"] and payload["open_id"] then
            return payload
        end
        return nil, "invalid token: " .. json.encode(result)
    end
    return nil, "invalid token: " .. json.encode(result)
end

使用 Cookie 存储登录凭证

ngx.header["Set-Cookie"] = self.cookie_key .. "=" .. token

组织架构白名单

我们在用户登录时获取用户的部门信息,或者在用户后续访问应用时解析登录凭证中的部门信息,根据设置的部门白名单,判断用户是否拥有访问应用的权限。

-- 部门白名单配置
_M.department_whitelist = {}

function _M:check_user_access(user)
    if type(self.department_whitelist) ~= "table" then
        ngx.log(ngx.ERR, "department_whitelist is not a table")
        return false
    end
    if #self.department_whitelist == 0 then
        return true
    end

    local department_ids = user["department_ids"]
    if not department_ids or department_ids == "" then
        return false
    end
    if type(department_ids) ~= "table" then
        department_ids = json.decode(department_ids)
    end
    for i=1, #department_ids do
        if has_value(self.department_whitelist, department_ids[i]) then
            return true
        end
    end
    return false
end

更多网关配置

同时支持 IP 黑名单和路由白名单配置。

-- IP 黑名单配置
_M.ip_blacklist = {}
-- 路由白名单配置
_M.uri_whitelist = {}

function _M:auth()
    local request_uri = ngx.var.uri
    ngx.log(ngx.ERR, "request uri: ", request_uri)

    if has_value(self.uri_whitelist, request_uri) then
        ngx.log(ngx.ERR, "uri in whitelist: ", request_uri)
        return
    end

    local request_ip = ngx.var.remote_addr
    if has_value(self.ip_blacklist, request_ip) then
        ngx.log(ngx.ERR, "forbided ip: ", request_ip)
        return ngx.exit(ngx.HTTP_FORBIDDEN)
    end

    if request_uri == self.logout_uri then
        return self:logout()
    end

    local payload, err = self:verify_token()
    if payload then
        if self:check_user_access(payload) then
            return
        end

        ngx.log(ngx.ERR, "user access not permitted")
        self:clear_token()
        return self:sso()
    end
    ngx.log(ngx.ERR, "verify token failed: ", err)

    if request_uri ~= self.callback_uri then
        return self:sso()
    end
    return self:sso_callback()
end

使用

本文就不赘述 OpenResty 的安装了,可以参考我的另一篇文章《在 Ubuntu 上使用源码安装 OpenResty》

下载

cd /path/to
git clone git@github.com:ledgetech/lua-resty-http.git
git clone git@github.com:SkyLothar/lua-resty-jwt.git
git clone git@github.com:k8scat/lua-resty-feishu-auth.git

配置

lua_package_path "/path/to/lua-resty-feishu-auth/lib/?.lua;/path/to/lua-resty-jwt/lib/?.lua;/path/to/lua-resty-http/lib/?.lua;/path/to/lua-resty-redis/lib/?.lua;/path/to/lua-resty-redis-lock/lib/?.lua;;";

server {
    access_by_lua_block {
        local feishu_auth = require "resty.feishu_auth"
        feishu_auth.app_id = ""
        feishu_auth.app_secret = ""
        feishu_auth.callback_uri = "/feishu_auth_callback"
        feishu_auth.logout_uri = "/feishu_auth_logout"
        feishu_auth.app_domain = "feishu-auth.example.com"

        feishu_auth.jwt_secret = "thisisjwtsecret"

        feishu_auth.ip_blacklist = {"47.1.2.3"}
        feishu_auth.uri_whitelist = {"/"}
        feishu_auth.department_whitelist = {"0"}

        feishu_auth:auth()
    }
}

配置说明

  • app_id 用于设置飞书企业自建应用的 App ID
  • app_secret 用于设置飞书企业自建应用的 App Secret
  • callback_uri 用于设置飞书网页登录后的回调地址(需在飞书企业自建应用的安全设置中设置重定向 URL)
  • logout_uri 用于设置登出地址
  • app_domain 用于设置访问域名(需和业务服务的访问域名一致)
  • jwt_secret 用于设置 JWT secret
  • ip_blacklist 用于设置 IP 黑名单
  • uri_whitelist 用于设置地址白名单,例如首页不需要登录认证
  • department_whitelist 用于设置部门白名单(字符串)

应用权限说明

  • 获取部门基础信息
  • 获取部门组织架构信息
  • 以应用身份读取通讯录
  • 获取用户组织架构信息
  • 获取用户基本信息

开源

本项目已完成且已在 GitHub 上开源:k8scat/lua-resty-feishu-auth,希望大家可以动动手指点个 Star,表示对本项目的肯定与支持!

目录
相关文章
|
6月前
|
数据采集 运维 监控
构建企业级Selenium爬虫:基于隧道代理的IP管理架构
构建企业级Selenium爬虫:基于隧道代理的IP管理架构
|
9月前
|
消息中间件 运维 监控
企业级短信验证码服务架构设计与最佳实践
随着移动互联网的发展,短信验证码成为用户身份验证的重要手段。本文从企业级应用角度出发,探讨如何构建高可用、高并发和安全可靠的短信验证码服务。通过多通道冗余、故障自动切换和服务降级保障高可用性;利用异步处理与消息队列应对高并发;借助多层防刷、内容审核和数据加密提升安全性。同时,提供了详细的架构设计、核心模块代码示例以及监控运维方案,帮助读者理解并实现一个完整的短信验证码系统。
472 2
|
9月前
|
存储 SQL 分布式计算
19章构建企业级大数据平台:从架构设计到数据治理的完整链路
开源社区: 贡献者路径:从提交Issue到成为Committer 会议演讲:通过DataWorks Summit提升影响力 标准制定: 白皮书撰写:通过DAMA数据治理框架认证 专利布局:通过架构设计专利构建技术壁垒
|
8月前
|
人工智能 监控 数据可视化
企业级LLMOps落地指南:蜂巢架构×可视化编排实战
本文将基础的单应用扩展成多应用,并实现工作流组件,包括:多应用模块设计、工作流模块设计、LangGraph实现图应用、前端Vue-Flow组件使用、工作流转LLM工具设计思路、关联工作流登技巧。
387 3
企业级LLMOps落地指南:蜂巢架构×可视化编排实战
|
6月前
|
存储 消息中间件 安全
企业级实时消息推送系统的架构设计,一文即懂!
如果你是技术负责人,该如何搭建一套能解决这些问题的企业级统一消息推送平台?今天我们就从核心挑战出发,拆解一套可落地的统一推送服务架构方案。
948 0
|
11月前
|
缓存 监控 安全
301重定向进阶指南:从基础配置到企业级架构优化
本文深入探讨网站重定向的高级技巧与企业级实现,涵盖正则表达式重定向、权重无损迁移、分布式系统适配等核心内容。通过解析301/302状态码区别及应用场景,结合Nginx、Apache配置示例,帮助开发者优化大规模网站重定向逻辑。同时,文章介绍CDN边缘重定向、微服务架构下的规则管理以及容灾设计,确保高性能与安全性。最后提供全链路监控方案和经典案例分析,助你规避流量损失风险,提升SEO表现。
468 38
|
8月前
|
消息中间件 人工智能 安全
企业级AI应用需要系统工程支撑,如何通过MCP大模型架构实现全链路实战解构?
本文三桥君深入探讨了MCP大模型架构在企业级AI应用中的全链路实战解构。从事件驱动、统一中台、多端接入、API网关、AI Agent核心引擎等九个核心模块出发,系统阐述了该架构如何实现低耦合高弹性的智能系统构建。AI专家三桥君提出从技术、内容、业务三个维度构建评估体系,为企业级AI应用提供了从架构设计到落地优化的完整解决方案。
408 0
|
11月前
|
监控 应用服务中间件 区块链
301重定向的终极指南:从基础配置到企业级架构设计
本文全面解析301重定向技术,从基础配置到企业级架构设计。涵盖HTTP状态码语义、浏览器与爬虫处理差异,提供分层架构模型及高可用配置示例。深入探讨亿级URL处理策略、流量压力测试数据,结合HTTP/2优化与Core Web Vitals提升方案。同时关注隐私合规性、故障排查工具及前沿技术融合,如机器学习预测和区块链存证。最后通过实际案例分析,展示重定向工程的商业价值与未来趋势。
315 14
|
11月前
|
人工智能 自然语言处理 物联网
如何成为企业级大模型架构师?
企业级大模型架构师需要掌握从 底层算力、模型训练、微调优化、推理部署、企业集成 到 安全合规 的全栈能力。这里提供一个完整的 企业级大模型架构师成长体系。
1212 4
|
JavaScript 前端开发 Java
Jeesite5:Star24k,Spring Boot 3.3+Vue3实战开源项目,架构深度拆解!让企业级项目开发效率提升300%的秘密武器
Jeesite5 是一个基于 Spring Boot 3.3 和 Vue3 的企业级快速开发平台,集成了众多优秀开源项目,如 MyBatis Plus、Bootstrap、JQuery 等。它提供了模块化设计、权限管理、多数据库支持、代码生成器和国际化等功能,极大地提高了企业级项目的开发效率。Jeesite5 广泛应用于企业管理系统、电商平台、客户关系管理和知识管理等领域。通过其强大的功能和灵活性,Jeesite5 成为了企业级开发的首选框架之一。访问 [Gitee 页面](https://gitee.com/thinkgem/jeesite5) 获取更多信息。
718 0
Jeesite5:Star24k,Spring Boot 3.3+Vue3实战开源项目,架构深度拆解!让企业级项目开发效率提升300%的秘密武器