专栏首页翻译计划飞书 + 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
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,表示对本项目的肯定与支持!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 微服务API网关-Kong初探

    Kong是一个clould-native、快速的、可扩展的、分布式的微服务抽象层(也称为API网关、API中间件或在某些情况下称为服务网格)框架。更确切地说,K...

    KaliArch
  • 一个高级应用设计概要:完整设计一个高级应用-第一篇

    版权说明:本文书写过程中参照了红帽的技术文档;本系列文章中的部分测试代码为红帽公司版权所有,因此不能提供源码文件。

    魏新宇
  • VMware描绘移动商务未来发展蓝图

    全球云基础架构和移动商务领导厂商VMware公司(NYSE:VMW)今日公布了对移动商务未来前景的展望,公司将通过全新解决方案、服务和合作伙伴关系帮助企业实现商...

    逸鹏
  • 精选的10款Java开源项目,建议收藏

    目前国内的IT开源环境还是相当不错,但是国内开发注重是应用,创新有但不多,从榜单可以看出,专门搞技术的还是少数,结合项目实践的占大多数,分享给大家,架构的时候可...

    攻城狮的那点事
  • 多租户用户管理数据模型设计

    公司建设一个SaaS平台,用于发布各种企业级的SaaS应用,需要新增一个多租户版本的用户管理系统,下面归纳总结下整个多租户版本的用户管理数据模型设计。

    java乐园
  • 私有云下的身份与管理解决方案

    信息化时代,企业分布式管理模式的广泛应用使当今的IT系统管理变得复杂,企业必须提供一个全方位的资源审视以确保企业资源的有效访问和管理。而云计算的不断发展使得众企...

    静一
  • 绿云获得ISO27001认证 | 腾讯SaaS加速器·学员动态

    ? 来源 | 腾讯SaaS加速器首期项目-绿云软件 ---- 近日,绿云软件ISO27001体系认证(信息安全管理体系规范)已顺利完成,获得经过CNCA国家认...

    腾讯SaaS加速器
  • SSL证书常见的八大误区

    自从18年2月初谷歌旗下Chrome浏览器宣布“封杀”HTTP将所有HTTP标示为不安全网站以后,越来越多的网站用户开始安装SSL证书,将网络传输协议从HTTP...

    合信ssl证书
  • Nginx 在运维领域中的应用,看这一篇就够了

    Nginx 已诞生十余年,其作为一款开源的 Web 服务器软件,因其具有性能稳定、高并发、低内存耗用、高性能的处理能力等特点,被广泛应用到国内外各互联网厂商的实...

    辞令
  • OA系统集成40多类业务软件,轻松实现平台化办公

    平台化的OA产品,以流程、门户、内容、集成、消息、组织、建模等7大引擎为基础,可以帮助客户统一人员管理、打造便捷的业务集成接口,与各类业务软件无缝集成,让客户在...

    泛微移动办公
  • 1-OpenResty 介绍 (摘抄)

    由于我的做小程序的时候改了下后台的安装软件,不是使用的单纯的Nginx,而是使用的OpenResty,然后呢在网上看到了有介绍OpenResty的,故摘抄下来,...

    杨奉武
  • 私有PaaS在乐视的实践

    PaaS在云平台中的作用 * 打通接入层、应用层、服务层 * 承载了云平台95%以上的业务

    字母哥博客
  • 说书解书:与时俱进的《技术管理之巅》——作者专访

    互联网行业奇缺CTO是现状,很多人正在到处物色CTO,那么到底如何成为一名合格的CTO呢?技术管理到底应该怎么做呢?《技术管理之巅》不仅解答了这些问题,还给出了...

    博文视点Broadview
  • 微服务架构下的统一身份认证和授权

    本文讨论基于微服务架构下的身份认证和用户授权的技术方案,在阅读之前,最好先熟悉并理解以下几个知识点:

    matinal
  • 企业区块链在2018年已经就绪

    Mark Rakhmilevich是Oracle公司区块链产品管理的高级总监。他致力于Oracle区块链云服务,并指导企业、isv和SIs构建区块链应用程序,并...

    首席架构师智库
  • ZABBIX对接飞书实现报警通知

    飞书提供了丰富的api来实现消息的通知,包括文本消息、图片消息、富文本消息,本次介绍使用飞书api发送文本消息,以下是实现思路 飞书API地址:https://...

    没有故事的陈师傅
  • 区块链+协同,打造企业应用创新与实践新路径

    区块链技术是一个分布式账本,一种通过去中心化、去唯一信任的方式集体维护一个可靠数据库的技术方案。比特币、ICO不是区块链的全部,区块链更大的空间和机会是各种各样...

    人称T客
  • 中台战略与气象业务系统建设之经验分享

    【中台】是2018年开始火爆起来的,最先实践的是阿里巴巴,那本《企业IT架构转型之道》成为阿里巴巴【中台】战略思想与架构实战的参考宝典,我也通过这本书才开始关注...

    用户1247399
  • 【黄埔】关于开展广东省2018年高企认定工作的通知

           根据《高新技术企业认定管理办法》(国科发火〔2016〕32号,以下简称《认定办法》)、《高新技术企业认定管理工作指引》(国科发火〔2016〕195...

    高新企业认定网

扫码关注云+社区

领取腾讯云代金券