如何让Slack斜杠命令请求携带用户Access Token而非仅验证令牌?
实现斜杠命令携带用户Access Token的方案
其实斜杠命令本身并不会直接携带用户的access token——因为Slack发送斜杠命令请求时,是从Slack服务器直接打给你的Express端点,不会带上用户端的token信息。不过我们可以通过关联用户Slack ID和已存储的access token来实现这个需求,具体步骤如下:
1. 授权时存储用户的Slack ID与Access Token
首先,当用户通过passport-slack完成OAuth授权后,你需要把用户的Slack唯一ID(profile.user.id)和对应的access token关联存储到数据库(比如MongoDB、PostgreSQL)或者缓存(比如Redis)里。
示例代码(passport-slack回调):
const SlackStrategy = require('passport-slack').Strategy; passport.use(new SlackStrategy({ clientID: process.env.SLACK_CLIENT_ID, clientSecret: process.env.SLACK_CLIENT_SECRET, callbackURL: "/auth/slack/callback", scope: ["commands", "users:read", "offline_access"] // offline_access用于获取refresh token }, async (accessToken, refreshToken, profile, done) => { try { // 用Slack用户ID查找或创建用户记录,存储token const user = await User.findOneAndUpdate( { slackUserId: profile.user.id }, { slackAccessToken: accessToken, slackRefreshToken: refreshToken // 存储refresh token用于后续刷新过期的access token }, { upsert: true, new: true } ); done(null, user); } catch (err) { done(err); } } ));
2. 处理斜杠命令请求时获取Access Token
当Slack向你的端点发送斜杠命令请求时,请求体里会包含user_id字段(用户在当前Slack团队的唯一ID)。我们可以利用这个ID,从存储中取出对应的access token。
不过第一步必须验证Slack的请求签名,这是Slack强制要求的安全措施,防止恶意伪造请求。
示例代码(Express斜杠命令端点):
const crypto = require('crypto'); const fetch = require('node-fetch'); // 验证Slack请求签名的工具函数 function verifySlackRequest(signature, timestamp, rawBody, signingSecret) { // 拒绝5分钟前的请求,防止重放攻击 const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 300; if (timestamp < fiveMinutesAgo) return false; const baseString = `v0:${timestamp}:${rawBody}`; const hmac = crypto.createHmac('sha256', signingSecret); const computedSignature = `v0=${hmac.update(baseString).digest('hex')}`; // 用timingSafeEqual避免时序攻击 return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computedSignature)); } app.post('/slack/commands/my-command', async (req, res) => { // 1. 验证请求合法性 const slackSignature = req.headers['x-slack-signature']; const slackTimestamp = req.headers['x-slack-request-timestamp']; // 注意:需要配置body-parser才能获取rawBody,比如: // app.use(bodyParser.urlencoded({ extended: true, verify: (req, res, buf) => req.rawBody = buf.toString() })); const rawBody = req.rawBody; if (!verifySlackRequest(slackSignature, slackTimestamp, rawBody, process.env.SLACK_SIGNING_SECRET)) { return res.status(403).send('Invalid request signature'); } // 2. 提取用户Slack ID const { user_id } = req.body; // 3. 从数据库获取对应的access token const user = await User.findOne({ slackUserId: user_id }); if (!user || !user.slackAccessToken) { // 用户未授权,引导完成授权 return res.send(`请先完成账号授权:<${process.env.BASE_URL}/auth/slack|点击这里>`); } // 4. 现在你可以使用access token调用Slack API了 try { const apiRes = await fetch('https://slack.com/api/users.info', { method: 'POST', headers: { 'Authorization': `Bearer ${user.slackAccessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ user: user_id }) }); const data = await apiRes.json(); if (data.ok) { res.send(`Hi ${data.user.real_name}! 已成功获取你的Access Token`); } else { // 如果token过期,尝试用refresh token刷新 if (data.error === 'invalid_auth') { // 这里可以添加刷新token的逻辑,更新数据库中的access token res.send('你的授权已过期,请重新授权'); } else { res.send('获取用户信息失败'); } } } catch (err) { res.send('服务器内部错误'); } });
关键注意事项
- 安全第一:一定要验证Slack的请求签名,并且加密存储access token,不要明文保存。
- Token刷新:如果用户长时间未使用,access token可能会过期,此时可以用之前存储的refresh token去Slack API刷新新的access token(需要授权时请求
offline_accessscope)。 - 用户引导:如果用户还未完成OAuth授权,要在斜杠命令的响应中引导用户去授权页面,否则无法获取到access token。
内容的提问来源于stack exchange,提问作者fstnboy
相关产品推荐
相关产品推荐

