Next.js中基于Referrer与User Agent生成并全局使用X-CSRF Token方案咨询
Next.js对接PHP API的CSRF认证解决方案
1. 生成CSRF并实现全局可用
第一步:编写统一的CSRF生成函数
确保客户端与PHP后端的MD5生成逻辑完全一致,兼容服务端(Node.js)和客户端环境:
// utils/csrf.ts import crypto from 'crypto'; import CryptoJS from 'crypto-js'; // 与PHP后端匹配的CSRF生成逻辑:Referrer + User Agent + 机密字符串 → MD5 export function generateCSRF(referrer: string, userAgent: string, secret: string): string { const input = `${referrer}|${userAgent}|${secret}`; // 服务端用Node原生crypto,客户端用CryptoJS if (typeof window === 'undefined') { return crypto.createHash('md5').update(input).digest('hex'); } else { return CryptoJS.MD5(input).toString(); } }
第二步:全局API请求封装
封装请求工具,自动生成并携带CSRF头,避免重复编写逻辑:
// utils/api.ts import { generateCSRF } from './csrf'; // 从环境变量读取机密字符串(客户端需用NEXT_PUBLIC_前缀暴露) const API_CSRF_SECRET = process.env.NEXT_PUBLIC_API_CSRF_SECRET!; export async function fetchAPI(url: string, options: RequestInit = {}) { let referrer = ''; let userAgent = ''; if (typeof window !== 'undefined') { // 客户端获取Referrer,无值时回退到当前域名 referrer = document.referrer || window.location.origin; userAgent = navigator.userAgent; } const csrf = generateCSRF(referrer, userAgent, API_CSRF_SECRET); const headers = { ...options.headers, 'X-CSRF': csrf, 'X-Referrer': referrer, 'User-Agent': userAgent }; return fetch(url, { ...options, headers }); }
可选:React Context全局注入
若组件需直接调用CSRF值,可通过Context全局注入:
// contexts/CSRFContext.tsx import { createContext, useContext, useEffect, useState } from 'react'; import { generateCSRF } from '../utils/csrf'; const CSRFContext = createContext<string | null>(null); export function CSRFProvider({ children }: { children: React.ReactNode }) { const [csrf, setCsrf] = useState<string | null>(null); const secret = process.env.NEXT_PUBLIC_API_CSRF_SECRET; useEffect(() => { if (typeof window !== 'undefined' && secret) { const referrer = document.referrer || window.location.origin; const userAgent = navigator.userAgent; setCsrf(generateCSRF(referrer, userAgent, secret)); } }, []); return <CSRFContext.Provider value={csrf}>{children}</CSRFContext.Provider>; } export const useCSRF = () => useContext(CSRFContext);
在_app.tsx中包裹全局组件:
// pages/_app.tsx import { CSRFProvider } from '../contexts/CSRFContext'; import type { AppProps } from 'next/app'; export default function App({ Component, pageProps }: AppProps) { return ( <CSRFProvider> <Component {...pageProps} /> </CSRFProvider> ); }
2. 在getServerSideProps中获取Referrer与User Agent
getServerSideProps运行在服务端,需从请求对象中读取头信息:
// pages/[page].tsx import { generateCSRF } from '../utils/csrf'; import fetch from 'node-fetch'; import type { GetServerSidePropsContext } from 'next'; export async function getServerSideProps(context: GetServerSidePropsContext) { const { req } = context; // HTTP头中Referrer拼写为referer,无值时回退到应用域名 const referrer = req.headers.referer || process.env.NEXT_PUBLIC_APP_URL!; const userAgent = req.headers['user-agent'] || ''; // 服务端机密字符串无需暴露给客户端,用普通环境变量 const secret = process.env.API_CSRF_SECRET!; const csrf = generateCSRF(referrer, userAgent, secret); // 调用PHP API并携带认证头 const res = await fetch(`${process.env.API_ENDPOINT}/your-path`, { headers: { 'X-CSRF': csrf, 'X-Referrer': referrer, 'User-Agent': userAgent } }); const data = await res.json(); return { props: { data } }; }
关键注意事项
- 确保PHP后端与客户端的机密字符串完全一致,客户端需用
NEXT_PUBLIC_前缀暴露该变量。 - HTTP头中Referrer的标准拼写是
referer,不要写成referrer。 - 处理头信息为空的场景,设置合理的回退值(如应用自身域名)。
内容的提问来源于stack exchange,提问作者Rahul Thakur
相关产品推荐
相关产品推荐

