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

NextJS+next-intl中间件IP拦截重定向报错及API封禁咨询

解决方案

一、修复next-intl报错并实现API驱动的IP封禁

1. 核心修改点

  • 将middleware改为异步函数以支持API请求
  • 所有路由(包括封禁重定向)必须经过intlMiddleware处理,确保locale上下文初始化
  • 替换原有的国家封禁逻辑为API查询IP状态
  • 处理API请求失败的fallback逻辑

2. 修改后的完整middleware代码

import createMiddleware from 'next-intl/middleware';
import { defaultLocale, locales, pathnames } from "./config/lang-config";
import withAuth from 'next-auth/middleware';
import { NextRequest, NextResponse } from 'next/server';
import { getProfile } from './services/authQueries';

const privatePages = [
    '/profile',
    '/settings',
    '/settings/*',
    '/wallet',
    '/wallet/*',
    '/auth/logout',
];

const intlMiddleware = createMiddleware({
    defaultLocale,
    locales,
    localePrefix: "as-needed",
    pathnames,
    localeDetection: true,
});

const authMiddleware = withAuth(
    async function onSuccess(req) {
        return intlMiddleware(req);
    },
    {
        callbacks: {
            authorized: async ({ token }: { token: any }) => {
                const accessToken = token?.tokenData?.token || token?.token as string;
                if (token) {
                    try {
                        const res = await getProfile(accessToken as string, 'en');
                        if (res.isSucceed) {
                            token.user = res.data;
                            return true;
                        } else {
                            throw new Error(res.message);
                        }
                    } catch (error) {
                        if (error instanceof Error) {
                            console.error(error.message);
                        } else {
                            console.error('An unknown error occurred');
                        }
                        return false;
                    }
                } else {
                    return false;
                }
            },
        },
        pages: {
            signIn: '/',
        }
    }
);

export default async function middleware(req: NextRequest) {
    const pathname = req.nextUrl.pathname;

    // 生产环境下检查IP封禁(排除/blocked页面避免循环重定向)
    if (process.env.NODE_ENV !== 'development' && pathname !== '/blocked') {
        // 获取用户真实IP(适配不同部署环境)
        const ip = req.ip 
            || req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() 
            || req.headers.get('x-real-ip');

        if (ip) {
            try {
                // 调用封禁检查API(替换为你的实际API地址)
                const blockResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/check-blocked-ip`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        // 内部API认证:添加密钥防止恶意调用
                        'Authorization': `Bearer ${process.env.INTERNAL_API_SECRET}`
                    },
                    body: JSON.stringify({ ip })
                });

                if (!blockResponse.ok) throw new Error('封禁检查API响应异常');
                
                const blockData = await blockResponse.json();
                if (blockData.isBlocked) {
                    // 先通过intlMiddleware初始化locale,再重定向到带locale的封禁页面
                    const intlResponse = intlMiddleware(req);
                    const baseUrl = new URL(req.url);
                    // 生成带正确locale的/blocked路径
                    const blockedPath = intlResponse.headers.get('location') 
                        ? `${intlResponse.headers.get('location')}/blocked`.replace(/\/+/g, '/')
                        : `/${defaultLocale}/blocked`;
                    intlResponse.headers.set('location', new URL(blockedPath, baseUrl).toString());
                    return intlResponse;
                }
            } catch (error) {
                console.error('IP封禁检查失败:', error);
                // 可选:API故障时的 fallback 逻辑,比如默认允许访问或临时封禁
                // return NextResponse.redirect(new URL('/blocked', req.url));
            }
        }
    }

    // 私有页面/公开页面路由处理
    const privatePathnameRegex = RegExp(
        `^(/(${locales.join('|')}))?(${privatePages
            .flatMap((p) => p.replace(/\*/g, '.*'))
            .map((p) => p === '/' ? ['', '/'] : p)
            .join('|')})/?$`,
        'i'
    );

    const isPrivatePage = privatePathnameRegex.test(req.nextUrl.pathname);

    if (!isPrivatePage) {
        const country = req.geo?.country 
            || req.headers.get('cloudfront-viewer-country') 
            || req.headers.get('x-vercel-ip-country');
        const response = intlMiddleware(req);
        // 修正拼写错误:client-counry → client-country
        response.cookies.set("client-country", country || '');
        return response;
    } else {
        // 异步处理authMiddleware
        return await (authMiddleware as any)(req);
    }
}

export const config = {
    matcher: [
        '/',
        '/(en-US)/:path*',
        '/((?!api|_next|_vercel|.*\\..*).*)'
    ]
};

二、API管控封禁用户的建议

  • API设计:
    • 接口接收ip参数,返回{ isBlocked: boolean, reason?: string }格式的响应
    • 加入API密钥认证,仅允许内部服务调用
  • 性能优化:
    • 用Redis缓存封禁IP的结果,设置15-30分钟的过期时间,减少API请求量
  • 容错机制:
    • API请求失败时,根据业务需求选择默认放行或临时封禁,避免影响正常用户访问
  • 日志记录:
    • 在middleware和API中记录封禁事件(IP、时间、原因),用于后续分析和合规检查
  • 封禁页面适配:
    • 确保/blocked页面支持多语言,在intl的pathnames配置中添加该页面的路由映射

内容的提问来源于stack exchange,提问作者Furkan Tombaş

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 19:32:04