如何在可分发Slack应用中程序化获取管理用户与多工作区Bot令牌?
我正在开发一款计划分发至多个组织/工作区的Slack应用,目前通过Slack OAuth2流程获取Bot令牌,该令牌具备正确权限可在工作区执行操作。但部分功能(如为用户设置提醒)需要用户令牌,这类令牌需在用户与应用交互时(如首次安装、打开主页或点击按钮)获取。我想了解如何获取、存储及复用用户令牌,同时如何管理不同工作区/组织的Bot令牌?我的后端是Python Flask应用。另外,我尝试用Bot令牌设置提醒时出现报错:slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/reminders.list) The server responded with: {'ok': False, 'error': 'not_allowed_token_type'}
一、报错原因说明
你遇到的not_allowed_token_type错误,核心原因是:Slack的reminders.*系列API仅支持用户令牌(User Token)调用,Bot令牌没有权限访问这类依赖用户身份的接口,必须切换为用户令牌才能操作提醒功能。
二、用户令牌的获取、存储与复用
1. 获取用户令牌
要获取用户令牌,需调整OAuth2授权流程,明确请求**用户范围(User Scopes)**的权限(比如reminders:write、reminders:read),在Flask中结合slack_sdk实现:
步骤1:生成授权URL(触发用户授权)
当用户触发交互(如点击按钮、打开应用主页)时,生成包含用户权限的授权URL,引导用户跳转授权:
from slack_sdk.oauth import AuthorizeUrlGenerator # 初始化授权URL生成器 authorize_url_generator = AuthorizeUrlGenerator( client_id="你的Slack应用Client ID", scopes=["bot:basic"], # Bot所需的权限范围 user_scopes=["reminders:write", "reminders:read"], # 用户令牌所需的权限范围 redirect_uri="https://你的Flask域名/slack/oauth/callback" # 回调地址 ) # 在路由中返回授权URL @app.route("/slack/authorize") def slack_authorize(): authorize_url = authorize_url_generator.generate() return redirect(authorize_url)
步骤2:处理OAuth回调,提取用户令牌
用户授权后,Slack会重定向到你的回调地址,此时可交换得到用户令牌:
from slack_sdk.oauth import OAuthV2AccessTokenExchangeAPI from flask import request, redirect @app.route("/slack/oauth/callback") def slack_oauth_callback(): code = request.args.get("code") # 交换令牌 client = WebClient() response = OAuthV2AccessTokenExchangeAPI(client).call( client_id="你的Slack应用Client ID", client_secret="你的Slack应用Client Secret", code=code, redirect_uri="https://你的Flask域名/slack/oauth/callback" ) # 提取关键信息 user_token = response["authed_user"]["access_token"] user_id = response["authed_user"]["id"] team_id = response["team"]["id"] refresh_token = response["authed_user"]["refresh_token"] expires_in = response["expires_in"] # 令牌有效期(默认1年) # 调用存储函数保存令牌(见下文) save_user_token(team_id, user_id, user_token, refresh_token, expires_in) return redirect("/操作成功页面")
2. 存储令牌
需要持久化存储(如PostgreSQL、MySQL、Redis),设计存储结构时需区分工作区、用户、令牌类型:
示例SQL表结构
CREATE TABLE slack_tokens ( id SERIAL PRIMARY KEY, team_id VARCHAR(255) NOT NULL, # 工作区ID,唯一标识工作区 user_id VARCHAR(255), # 用户ID,用户令牌必填,Bot令牌可为空 token_type VARCHAR(50) NOT NULL CHECK (token_type IN ('user', 'bot')), access_token TEXT NOT NULL, # 令牌内容 refresh_token TEXT, # 刷新令牌,用于更新过期令牌 expires_at TIMESTAMP NOT NULL, # 令牌过期时间 UNIQUE(team_id, user_id, token_type) # 确保同一用户+工作区+令牌类型唯一 );
Flask中用SQLAlchemy实现存储
from flask_sqlalchemy import SQLAlchemy from datetime import datetime, timedelta db = SQLAlchemy(app) class SlackToken(db.Model): id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.String(255), nullable=False) user_id = db.Column(db.String(255)) token_type = db.Column(db.String(50), nullable=False) access_token = db.Column(db.Text, nullable=False) refresh_token = db.Column(db.Text) expires_at = db.Column(db.DateTime, nullable=False) __table_args__ = ( db.UniqueConstraint('team_id', 'user_id', 'token_type', name='_team_user_token_uc'), ) # 保存用户令牌的函数 def save_user_token(team_id, user_id, access_token, refresh_token, expires_in): expires_at = datetime.utcnow() + timedelta(seconds=expires_in) # 检查是否已有该用户的令牌,有则更新,无则新增 token = SlackToken.query.filter_by(team_id=team_id, user_id=user_id, token_type='user').first() if token: token.access_token = access_token token.refresh_token = refresh_token token.expires_at = expires_at else: token = SlackToken( team_id=team_id, user_id=user_id, token_type='user', access_token=access_token, refresh_token=refresh_token, expires_at=expires_at ) db.session.add(token) db.session.commit()
3. 复用用户令牌
调用API前先从存储中取出有效令牌,若过期则自动刷新:
from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # 获取有效用户令牌的函数 def get_valid_user_token(team_id, user_id): token = SlackToken.query.filter_by(team_id=team_id, user_id=user_id, token_type='user').first() if not token: return None # 用户未授权,需引导重新授权 # 检查令牌是否过期 if datetime.utcnow() >= token.expires_at: # 刷新令牌 client = WebClient() try: response = client.oauth_v2_access( client_id="你的Slack应用Client ID", client_secret="你的Slack应用Client Secret", refresh_token=token.refresh_token, grant_type="refresh_token" ) # 更新存储中的令牌信息 token.access_token = response["authed_user"]["access_token"] token.refresh_token = response["authed_user"]["refresh_token"] token.expires_at = datetime.utcnow() + timedelta(seconds=response["expires_in"]) db.session.commit() except SlackApiError as e: return None # 刷新失败,需引导用户重新授权 return token.access_token # 调用reminders.list的示例 def list_user_reminders(team_id, user_id): user_token = get_valid_user_token(team_id, user_id) if not user_token: return {"error": "用户未授权或令牌失效"} client = WebClient(token=user_token) try: response = client.reminders_list() return response except SlackApiError as e: return {"error": str(e)}
三、多工作区Bot令牌的管理
1. 存储策略
每个工作区对应一个Bot令牌,同样存储在slack_tokens表中(token_type='bot',user_id为空),用team_id作为唯一标识。
2. 获取与刷新
首次安装应用时,OAuth回调会返回Bot令牌,直接调用存储函数保存;令牌过期时,用刷新令牌调用oauth.v2.access接口刷新。
3. 使用示例
def get_valid_bot_token(team_id): token = SlackToken.query.filter_by(team_id=team_id, token_type='bot').first() if not token: return None # 工作区未安装应用 if datetime.utcnow() >= token.expires_at: client = WebClient() try: response = client.oauth_v2_access( client_id="你的Slack应用Client ID", client_secret="你的Slack应用Client Secret", refresh_token=token.refresh_token, grant_type="refresh_token" ) token.access_token = response["access_token"] token.refresh_token = response["refresh_token"] token.expires_at = datetime.utcnow() + timedelta(seconds=response["expires_in"]) db.session.commit() except SlackApiError as e: return None return token.access_token # 发送频道消息的示例(使用Bot令牌) def send_channel_message(team_id, channel_id, text): bot_token = get_valid_bot_token(team_id) if not bot_token: return {"error": "工作区未安装应用"} client = WebClient(token=bot_token) try: response = client.chat_postMessage(channel=channel_id, text=text) return response except SlackApiError as e: return {"error": str(e)}
内容的提问来源于stack exchange,提问作者TheProductGuy44

