跨域场景下Vue SPA表单如何向异源PHPMailer发送CSRF Token
跨域分离部署下Vue SPA对接PHPMailer的CSRF Token实现方案
跨域分域场景下基于Session的CSRF方案天然失效:浏览器默认不会在跨域请求中携带第三方域名的Cookie,PHPMailer服务端拿不到Session标识,自然无法匹配Session中存储的校验值,不需要硬套同域部署的实现逻辑,直接用基于自定义请求头的无Cookie校验方案即可,实现步骤如下:
1. 先配置PHPMailer服务端的CORS基础规则
所有跨域请求的校验前提是CORS配置正确,不要用通配符放开权限:
- 明确设置
Access-Control-Allow-Origin为你的Vue前端实际部署域名,禁止配置为* - 设置
Access-Control-Allow-Headers,仅放开你需要用到的请求头,比如Content-Type、自定义的X-CSRF-Token - 不需要开启
Access-Control-Allow-Credentials,整个流程不需要传递Cookie,省掉SameSite、跨域凭证携带的一堆兼容问题
原理说明:自定义请求头的跨域请求会自动触发CORS预检(OPTIONS请求),只要你不随意放开允许的请求源,恶意站点根本无法在浏览器环境中向你的邮件接口发起带合法自定义头的请求,天然具备CSRF防护能力。
2. 服务端CSRF Token生成与存储逻辑
不要把Token存在Session里,换成服务端临时缓存即可:
- 单独提供一个GET类型的Token获取接口,比如
/get-csrf-token - 接口收到请求后,用密码学安全的随机函数生成至少32位长度的随机字符串作为Token,不要用时间戳、自增ID这类可预测的内容
- 将Token存入服务端临时缓存(Redis、本地文件缓存都可以),设置5-10分钟的过期时间,同时绑定请求来源IP、User-Agent作为辅助校验维度
- 将生成的Token明文返回给前端
- 注意不要在前端生成Token,前端生成的内容服务端没有存根,没有任何校验意义
3. Vue侧的Token获取与传递逻辑
- 在表单组件挂载、或者用户进入表单页路由时,发起GET请求到上面的Token接口,拿到返回的Token存在前端内存中即可(Pinia/Vuex状态、组件内部ref变量都可以,不需要刻意存在localStorage/SessionStorage,当然存了也不影响CSRF防护效果)
- 给对接PHPMailer的axios实例配置请求拦截器,所有发往邮件服务的POST请求,都在请求头中携带拿到的Token,示例代码:
import axios from 'axios' // 创建专门对接邮件服务的axios实例 const mailRequest = axios.create({ baseURL: 'https://你的PHPMailer服务域名' }) // 请求拦截器统一注入CSRF头 mailRequest.interceptors.request.use(config => { // csrfToken为之前存在状态中的Token值 config.headers['X-CSRF-Token'] = csrfToken.value return config })
- 表单提交时正常调用接口传表单数据即可,不需要开启axios的
withCredentials配置。
4. 服务端提交时的校验逻辑
收到表单POST请求时按顺序做校验:
- 先响应OPTIONS预检请求,返回符合之前配置的CORS头
- 读取POST请求头中的
X-CSRF-Token字段,判断该值是否存在于临时缓存中、是否在有效期内 - 匹配当前请求的IP、User-Agent是否和生成Token时记录的一致
- 所有校验通过再执行PHPMailer发信逻辑,校验不通过直接返回403状态码
- 发信完成后立刻删除缓存中对应的Token,保证Token一次性使用,避免被重复盗用
额外加固建议
- 给邮件接口加提交频率限制,比如单IP1分钟内最多允许提交3次,防止接口被恶意刷量
- 不要放开CORS的通配符配置,生产环境所有跨域允许的源、请求头都要做严格白名单限制
- 如果要防XSS偷Token,可以额外给接口加验证码、滑动校验等人机验证逻辑,和CSRF防护不冲突
内容的提问来源于stack exchange,提问作者moonris
相关产品推荐
相关产品推荐

