#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 飞书免登认证服务 运行: python3 feishu_auth.py 端口: 5000 """ from flask import Flask, request, jsonify import hashlib import hmac import time import requests import base64 app = Flask(__name__) # 飞书应用配置 FEISHU_APP_ID = 'cli_aab34d0ca3bb9bd4' FEISHU_APP_SECRET = '3GLsoqQZZ2N4sibP2DdKaeDdj65DSR5D' # 缓存 access_token _access_token = None _token_expire = 0 def get_access_token(): global _access_token, _token_expire if _access_token and time.time() < _token_expire: return _access_token resp = requests.post('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', json={ 'app_id': FEISHU_APP_ID, 'app_secret': FEISHU_APP_SECRET }) data = resp.json() if data.get('code') == 0: _access_token = data['tenant_access_token'] _token_expire = time.time() + data['expire'] - 300 return _access_token return None @app.route('/feishu/signature', methods=['GET']) def get_signature(): """生成 JS SDK 签名""" url = request.args.get('url', '') if not url: return jsonify({'error': 'url required'}), 400 timestamp = str(int(time.time())) nonce_str = 'ys' + timestamp[-6:] # 飞书签名格式 string_to_sign = f"{timestamp}\n{nonce_str}\n" # HMAC-SHA256 -> base64 sig_bytes = hmac.new( FEISHU_APP_SECRET.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256 ).digest() signature = base64.b64encode(sig_bytes).decode('utf-8') print(f"[Feishu] URL: {url}") print(f"[Feishu] timestamp: {timestamp}, nonceStr: {nonce_str}") print(f"[Feishu] string_to_sign: {repr(string_to_sign)}") print(f"[Feishu] signature (base64): {signature}") return jsonify({ 'appId': FEISHU_APP_ID, 'timestamp': int(timestamp), 'nonceStr': nonce_str, 'signature': signature }) @app.route('/feishu/callback', methods=['GET', 'POST']) def feishu_callback(): """OAuth 回调""" code = request.args.get('code') if request.is_json: code = request.json.get('code', code) if not code: return jsonify({'error': 'code required'}), 400 token = get_access_token() if not token: return jsonify({'error': 'failed to get access token'}), 500 # 获取 user_access_token resp = requests.post('https://open.feishu.cn/open-apis/authen/v1/oidc/access_token', headers={'Authorization': f'Bearer {token}'}, json={'grant_type': 'authorization_code', 'code': code} ) data = resp.json() if data.get('code') != 0: return jsonify({'error': data.get('msg', 'failed')}), 400 return jsonify(data['data']) @app.route('/feishu/oauth', methods=['GET']) def feishu_oauth(): """OAuth 换取用户信息""" code = request.args.get('code') if not code: return jsonify({'error': 'code required'}), 400 token = get_access_token() if not token: return jsonify({'error': 'failed to get access token'}), 500 # 获取 user_access_token resp = requests.post('https://open.feishu.cn/open-apis/authen/v1/oidc/access_token', headers={'Authorization': f'Bearer {token}'}, json={'grant_type': 'authorization_code', 'code': code} ) data = resp.json() print(f"[Feishu OAuth] access_token response: {data}") if data.get('code') != 0: return jsonify({'error': data.get('msg', 'failed to get access token')}), 400 user_data = data.get('data', {}) return jsonify({ 'open_id': user_data.get('open_id'), 'name': user_data.get('name'), 'email': user_data.get('email'), 'user_id': user_data.get('user_id') }) @app.route('/feishu/user/', methods=['GET']) def get_user(open_id): """获取飞书用户详情""" token = get_access_token() if not token: return jsonify({'error': 'no token'}), 500 resp = requests.get(f'https://open.feishu.cn/open-apis/contact/v3/users/{open_id}?user_id_type=open_id', headers={'Authorization': f'Bearer {token}'} ) data = resp.json() if data.get('code') == 0: return jsonify(data['data']['user']) return jsonify({'error': 'user not found'}), 404 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)