如何在Azure Function重定向响应中以Cookie传递Token?
问题分析
跨域场景下,浏览器会拦截302响应中的Set-Cookie头,这是同源策略的安全限制——除非Cookie配置了符合跨域要求的属性,或者采用其他安全的Token传递方式。
可行解决方案
方案1:返回前端跳转页面(通过POST传递Token)
放弃直接302重定向,改为返回一个包含自动提交表单的HTML页面,通过前端发起POST请求将Token安全传递到目标域名,目标域名后端接收后设置Cookie。
修改后的代码示例:
const { SAMLResponse } = qs.parse(context.req.body) const { clientName } = context.req.params try { const samlResponseFormatted = decodeURIComponent(SAMLResponse as string) logger.add(`SAML response formatted: ${samlResponseFormatted}`) const auth = await new SsoAuthenticate().getAuth(samlResponseFormatted, clientName) // 生成自动提交的表单页面 const redirectHtml = ` <!DOCTYPE html> <html> <body> <form id="tokenForm" method="POST" action="${auth?.redirectUrl}"> <input type="hidden" name="token" value="${auth?.token}"> </form> <script> document.getElementById('tokenForm').submit(); </script> </body> </html> ` context.res = { status: StatusCodes.OK, headers: { 'Content-Type': 'text/html' }, body: redirectHtml } } catch (err) { // 补充错误处理逻辑 }
目标域名后端需新增接收接口:
// 目标站后端示例(Express) app.post('/auth/callback', (req, res) => { const { token } = req.body; const expirationDate = new Date(Date.now() + 24 * 60 * 60 * 1000); res.cookie('token', token, { expires: expirationDate, httpOnly: true, secure: true, // 生产环境必须开启HTTPS sameSite: 'Lax' }); res.redirect('/'); });
方案2:配置Cookie跨域属性(限同主域子域场景)
如果当前域名和目标域名属于同一主域下的子域(如auth.example.com和app.example.com),可通过配置Cookie的跨域属性,让302重定向时Cookie生效。
修改原代码的Set-Cookie配置:
const expirationDate = new Date(Date.now()) expirationDate.setHours(expirationDate.getHours() + 24) const headers = { Location: `${auth?.redirectUrl}`, "Set-Cookie": `token=${auth?.token}; Expires=${expirationDate.toUTCString()}; Domain=.example.com; Path=/; Secure; SameSite=None; HttpOnly;` }
注意事项:
- 必须使用HTTPS协议(
Secure属性强制要求) SameSite=None必须配合Secure,否则浏览器会忽略该CookieDomain需设置为共同主域,确保所有子域都能访问
方案3:后端临时存储+一次性校验码(无明文Token暴露)
- 当前服务将Token存入临时存储(如Redis),设置短过期时间(比如5分钟),生成唯一一次性校验码作为Key
- 重定向到目标URL时携带校验码(路径参数或查询参数,因一次性使用,安全性可控)
- 目标域名后端通过校验码取出Token,设置Cookie后立即删除校验码
修改后的代码示例:
const { v4: uuidv4 } = require('uuid'); const redis = require('./redis-client'); // 已配置的Redis客户端 const { SAMLResponse } = qs.parse(context.req.body) const { clientName } = context.req.params try { const samlResponseFormatted = decodeURIComponent(SAMLResponse as string) logger.add(`SAML response formatted: ${samlResponseFormatted}`) const auth = await new SsoAuthenticate().getAuth(samlResponseFormatted, clientName) // 生成校验码并存入Redis,5分钟过期 const verifyCode = uuidv4(); await redis.setEx(verifyCode, 300, auth?.token); // 重定向到带校验码的目标地址 const redirectUrl = `${auth?.redirectUrl}/auth/verify/${verifyCode}`; context.res = { status: StatusCodes.MOVED_TEMPORARILY, headers: { Location: redirectUrl } } } catch (err) { // 补充错误处理逻辑 }
目标域名后端校验逻辑:
// 目标站后端示例(Express) app.get('/auth/verify/:code', async (req, res) => { const { code } = req.params; const token = await redis.get(code); if (!token) { return res.status(400).send('无效或过期的校验码'); } // 删除校验码,确保一次性使用 await redis.del(code); // 设置Cookie const expirationDate = new Date(Date.now() + 24 * 60 * 60 * 1000); res.cookie('token', token, { expires: expirationDate, httpOnly: true, secure: true, sameSite: 'Lax' }); res.redirect('/'); });
方案选择建议
- 无域名关联场景:优先选方案1或方案3
- 同主域子域场景:优先选方案2,配置简单无需额外开发
- 高安全性要求场景:优先选方案3,Token全程不暴露在前端
内容的提问来源于stack exchange,提问作者Ericson Willians
相关产品推荐
相关产品推荐

