跳转至

自建服务无痛接入企业微信扫码认证

想要为你的应用增加企业微信扫码登录功能?本教程手把手教你如何使用 Nginx 和 Flask 搭建一个安全可靠的认证系统。无需繁琐配置,快速实现企业微信用户身份验证,提升用户体验!

一、准备工作

  • 服务器: 一台能够运行 Nginx 和 Python 的服务器。
  • 域名: 已备案的域名,并解析到你的服务器 IP 地址。(例如:login.tech.intra.qq.com
  • 企业微信: 已开通的企业微信账号,并创建应用,获取 AgentIDCorpIDSecret

二、配置 Nginx

Nginx 作为反向代理服务器,负责拦截未认证的请求,并将用户重定向到企业微信扫码登录页面。

  1. 修改 Nginx 配置文件

    upstream auth_backend {
        server 10.44.3.7:39891; # Flask 应用的 IP 地址和端口
    }
    server {
        listen 80;
        server_name help_qice.tech.intra.qq.com; # 你的域名
        expires 1m;
    
        # 鉴权验证接口(内部调用)
        location /auth/ {
            proxy_pass http://auth_backend/auth/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_redirect off;
        }
    
        # 需要鉴权的路径(例如:/chat/ 及其子路径)
        location ~ ^/chat/[^/]+$ {
            auth_request /auth/verify; # 访问 /auth/verify 接口进行鉴权
            error_page 401 = @login;   # 鉴权失败,重定向到 @login
            proxy_pass http://10.66.244.44:9098; # 代理到后端主路径
        }
    
        # 登录重定向
        location @login {
            return 302 /auth/login?next=$request_uri; # 重定向到 /auth/login,并传递原始请求 URI
        }
        # admin
        location /admin/ {
           return 302 https://wework.qpic.cn/wwpic3az/653855_6HL_o5VRSWmKTJY_1754391309; proxy_pass http://10.66.244.44:9098/admin;
        }
    
        # 其他静态资源或无需鉴权的路径
        location / {
            add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
            add_header Pragma "no-cache";
            add_header Expires "0";
            proxy_pass http://10.66.244.44:9098/;  # 代理到后端主路径
        }
    }
    
    • server_name: 改为你的域名。
    • auth_backend: 改为运行 Flask 应用的服务器 IP 地址和端口。
    • location ~ ^/chat/[^/]+$: 定义需要进行企业微信认证的路径。可以根据你的实际需求修改。
    • proxy_pass http://10.66.244.44:9098/;: 你的应用后端的地址。
    • 保存并重启 Nginx
    sudo nginx -t  # 检查配置是否正确
    sudo nginx -s reload # 重启 Nginx
    

三、编写 Flask 认证应用 (auth.py)

Flask 应用负责处理企业微信扫码登录流程,验证用户身份,并设置 Session。

  1. 安装 Flask 和 requests 库

    pip install Flask requests
    
  2. 创建 auth.py 文件

    from flask import Flask, request, redirect, session, url_for, jsonify
    import requests
    import os
    
    app = Flask(__name__)
    app.secret_key = 'your_secure_secret_key_here'  # 请替换为安全密钥
    
    # 企微扫码登录配置
    WEIXIN_AGENTID = 123456  # 你的 AgentID
    WEIXIN_CORPID = "wx123456"  # 你的 CorpID
    WEIXIN_SECRET = 'AAAAAAAAAAAAAAAAAAAAAAAAAAA'  # 你的 Secret
    REDIRECT_URI = 'http://login.tech.intra.qq.com/help_qice/auth/callback'  # 替换为你的回调域名
    STATE = 'auth_state'                        # 防CSRF,建议每次动态生成
    
    # 获取 access_token(用于后续API调用)
    def get_access_token():
        url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={WEIXIN_CORPID}&corpsecret={WEIXIN_SECRET}"
        resp = requests.get(url)
        data = resp.json()
        if data['errcode'] == 0:
            return data['access_token']
        else:
            return None
    
    # 主页或测试
    @app.route('/')
    def index():
        return '''
        <h2>认证系统</h2>
        <p><a href="/chat">访问 /chat(触发鉴权)</a></p>
        '''
    
    # 扫码登录入口
    @app.route('/auth/login')
    def login():
        # 构造扫码登录 URL
        print(session)
        redirect_url = f"https://login.work.weixin.qq.com/wwlogin/sso/login?login_type=CorpApp&appid={WEIXIN_CORPID}&agentid={WEIXIN_AGENTID}&redirect_uri={REDIRECT_URI}&state=STATE"
        next_url = request.args.get('next', '/chat')  # 默认 fallback
        session['next'] = next_url  # 保存跳转目标
        return redirect(redirect_url)
    
    # 扫码回调
    @app.route('/auth/callback')
    def callback():
        code = request.args.get('code')
        state = request.args.get('state')
    
        if not code:
            return "登录失败:缺少 code", 400
        # 获取 access token
        access_token = get_access_token()
        if not access_token:
            return "无法获取 access_token", 500
    
        # 使用 code 换取用户信息
        user_info_url = f"https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token={access_token}&code={code}"
        resp = requests.get(user_info_url)
        data = resp.json()
    
        if data.get('errcode') != 0:
            return f"获取用户信息失败:{data.get('errmsg')}", 403
    
        user_id = data.get('UserId')  # 内部成员
        # open_userid = data.get('OpenUserId')  # 外部用户(如有)
    
        if not user_id:
            return "未获取到用户身份", 403
    
        # 认证成功,写入 session
        session['user_id'] = user_id
    
        next_url = session.pop('next', '/chat')  # 取出并删除
        return redirect(next_url)
    
    # Nginx auth_request 调用的验证接口
    @app.route('/auth/verify')
    def verify():
        print(session)
        if 'user_id' in session:
            return '', 200  # 成功,允许访问
        else:
            return 'Unauthorized', 401  # 拒绝,触发登录
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=39891, debug=True)  # 开发 HTTPS
    
    • app.secret_key: 设置一个安全的随机字符串,用于 Session 加密。
    • WEIXIN_AGENTID, WEIXIN_CORPID, WEIXIN_SECRET: 替换为你在企业微信后台获取的参数。
    • REDIRECT_URI: 重要! 必须与你在企业微信后台配置的回调域名一致。
    • /auth/login: 构造企业微信扫码登录 URL,并将用户重定向到该 URL。
    • /auth/callback: 处理企业微信扫码登录回调,获取用户信息,并设置 Session。
    • /auth/verify: Nginx 用于验证用户是否已登录的接口。
    • 运行 Flask 应用
    python auth.py
    

四、测试

  1. 访问需要认证的路径

    在浏览器中访问你的域名下需要认证的路径(例如:http://help_qice.tech.intra.qq.com/chat/demo)。

  2. 跳转到企业微信扫码登录页面

    如果未登录,你将会被重定向到企业微信扫码登录页面。

  3. 扫码登录

    使用企业微信 App 扫码登录。

  4. 认证成功

    如果认证成功,你将会被重定向到你之前访问的页面。

五、小技巧 企微的回调地址一般只能配置一个,此时我们可以配置为 http://login.tech.intra.qq.com
通过下面的操作可以很轻松的让多个独立的服务使用同一个回调地址

server {
    listen 80;
    server_name login.tech.intra.qq.com;# 优先处理的重定向
    location / {
        # 登录重定向专用  http://login.tech.intra.qq.com/pbc/view_svn_log/4   ==》 http://pbc.tech.intra.qq.com/view_svn_log/4
        rewrite ^/([^/]+)/(.+)$ http://$1.tech.intra.qq.com/$2 last;
    }
}


重定向逻辑(rewrite 指令)详细解释

rewrite 指令:

rewrite ^/([^/]+)/(.+)$ http://$1.tech.intra.qq.com/$2 last;:

正则表达式匹配:^/([^/]+)/(.+)$

    ^/:匹配路径以 / 开头(所有 URI 路径都以 / 开头)。
    ([^/]+):捕获第一个非 / 字符的序列(即第一个路径段落,例如 pbc)。括号表示捕获组,稍后用 $1 引用。
    /:匹配路径分隔符。
    (.+):捕获剩余的所有路径(包括查询参数,如果有)。用 $2 引用。
    $:匹配路径结束,确保整个路径都被捕获。
示例匹配:

输入 URL:http://login.tech.intra.qq.com/pbc/view_svn_log/4

    $1 = pbc
    $2 = view_svn_log/4


    输入 URL:http://login.tech.intra.qq.com/api/v1/data?param=1

    $1 = api
    $2 = v1/data?param=1(查询参数会被保留)

使用捕获组构建新 URL:将 $1 作为子域名,附加 .tech.intra.qq.com,然后附加剩余路径 $2。
协议固定为 http://,端口默认为 80(如果目标服务器监听其他端口,需要调整)。
示例重定向:

原:http://login.tech.intra.qq.com/pbc/view_svn_log/4
新:http://pbc.tech.intra.qq.com/view_svn_log/4
这实现了从“登录域 + 子路径”到“子域 + 剩余路径”的转换,注释中明确给出了这个例子,表明这是“登录重定向专用”。

标志:last

表示在当前 server 上下文中停止进一步的重写处理,并使用新 URL 重新开始请求处理(可能在同一个 Nginx 实例中匹配其他 server/location)。
如果使用 permanent 或 redirect,它会发送 301/302 重定向响应给客户端(浏览器会跟随重定向)。但这里用 last,表示内部重写(Nginx 内部处理,不发送重定向给客户端),适合在同一服务器上继续处理或代理。
注意:如果目标 URL 匹配其他 server 块,Nginx 会内部跳转;否则,可能导致循环或错误。