You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在可分发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:13:23