文章
nextjs 解决登录认证失败跳转登录问题
问题描述,nextjs区分server端以及client端,server端可以直接携带token访问接口,如果这时候token过期失效了,那么需要跳转到登录页面。另外在浏览器页面一个页面可能有很多client端接口访问api,如果token过期失效了,完成了一部分剩下了一部分如何跳转到登录页,以上跳转登录页还要给个提示说明跳转原因。
解决办法:
针对server端,在layout中全局获取用户信息,获取不到就跳转到登录页面,并附带原因
export default async function DashboardLayout({ children }: { children: ReactNode }) {
// 服务端获取当前登录用户信息
const user = await fetchCurrentUser();
if(!user) {
redirect("/login?status=unauthenticated")
}
...
}proxy.ts处理
// 未登录:重定向到登录页
if (!accessToken) {
const loginUrl = new URL('/login', req.url);
loginUrl.searchParams.set('next', path);
loginUrl.searchParams.set('status', 'unauthenticated');
return NextResponse.redirect(loginUrl); // 直接传 URL 对象即可
}针对client端,拦截401错误,刷新页面,触发页面重新加载,如果有token但是无效,会被server端layout拦截并跳转到登录页面,如果token过期了没了,那么会被中间件proxy拦截并跳转到登录页面
export async function requestAction(
context: RequestContext,
path: string,
init?: RequestInit
): Promise<ApiResponseType> {
let response: Response;
try {
if (context === 'server') {
response = await fetchWithAuth(path, init);
} else {
// client: 调用 /api/proxy/... 路由(由调用方确保 path 正确)
response = await fetch(path, {
...init,
headers: {
'Content-Type': 'application/json',
...init?.headers,
},
});
}
if (response.status === 401 && context === 'client') {
window.location.reload();
}
...
}登录页面消息处理
export default function Page() {
const searchParams = useSearchParams();
const status = searchParams.get("status")
useEffect(() => {
if (status === "unauthenticated") {
toast.message("登录已过期,请重新登录。", {
description: "您的登录凭证已失效,请输入账号密码重新登录。",
duration: 3000,
icon: "🔒"
});
}
}, [status]);
...
}springboot jwt验证修改:
public class JwtValidator extends OncePerRequestFilter {
// 定义哪些路径需要 JWT(和 SecurityConfig 保持一致)
private static final AntPathRequestMatcher PROTECTED_PATHS =
new AntPathRequestMatcher("/api/**");
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
boolean needsAuth = PROTECTED_PATHS.matches(request);
// 从请求头中提取 JWT
String jwt = request.getHeader(JwtConstant.JWT_HEADER);
if (needsAuth && (jwt == null || !jwt.startsWith("Bearer "))) {
// 主动拒绝:返回 401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\":\"认证缺失,请重新登录。\"}");
return; // 不再继续
}
if (jwt != null) {
//Bearer jwt 去掉 "Bearer " 前缀
jwt = jwt.substring(7);
try {
...
} catch (Exception e) {
// 直接返回 401,不要抛异常!
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\":\"无效或过期的认证凭证,请重新登录。\"}");
return; // 注意:这里 return,不再继续 filterChain
}
}
// 必须调用!
// 表示“JWT 验证完成,继续处理请求”(比如进入 Controller)。
filterChain.doFilter(request, response);
}
}- 添加PROTECTED_PATHS 定义哪些路由需要jwt验证
- 对需要jwt验证的页面校验jwt是否存在以及jwt是否有效