如何在Next.js中使用Keycloak实现自定义受保护路由及未认证重定向?
在Next.js中用Keycloak保护路由并实现未认证重定向
以下是几种适配不同Next.js路由架构的实现方案,你可以根据自己的项目选择:
一、Pages Router:使用高阶组件(HOC)
适合传统Pages Router项目,通过包裹页面组件实现保护。
1. 完善AuthProvider的上下文逻辑
确保AuthContext能提供认证状态和登录方法:
// contexts/AuthContext.js import { createContext, useContext, useEffect, useState } from 'react'; import Keycloak from 'keycloak-js'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [keycloak, setKeycloak] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { const initKeycloak = async () => { const kc = new Keycloak('/keycloak.json'); // 你的Keycloak配置文件路径 try { const authenticated = await kc.init({ onLoad: 'check-sso', // 静默检查已有会话 silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html` }); setKeycloak(kc); setIsAuthenticated(authenticated); } catch (err) { console.error('Keycloak初始化失败', err); } finally { setLoading(false); } }; initKeycloak(); }, []); const login = () => { // 登录后跳回当前受保护路由 keycloak.login({ redirectUri: `${window.location.origin}${window.location.pathname}` }); }; if (loading) return <div>加载中...</div>; return ( <AuthContext.Provider value={{ isAuthenticated, login }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
2. 编写路由保护高阶组件
// hoc/withAuth.js import { useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext'; const withAuth = (WrappedComponent) => { return (props) => { const { isAuthenticated, login } = useAuth(); useEffect(() => { if (!isAuthenticated) login(); }, [isAuthenticated, login]); if (!isAuthenticated) return null; // 跳转前可替换为加载提示 return <WrappedComponent {...props} />; }; }; export default withAuth;
3. 保护目标路由
在pages/contact.js中使用HOC包裹页面:
// pages/contact.js import withAuth from '../hoc/withAuth'; const ContactPage = () => { return <div>仅认证用户可见的联系页面</div>; }; export default withAuth(ContactPage);
二、全局路由保护:使用Next.js Middleware
适合同时保护多个路由,在服务器端拦截请求,适配Pages/App Router。
在项目根目录创建middleware.js:
// middleware.js import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // 定义需要保护的路由列表 const protectedRoutes = ['/contact']; const isProtected = protectedRoutes.some(route => request.nextUrl.pathname.startsWith(route)); if (!isProtected) return NextResponse.next(); // 检查Keycloak会话Cookie(名称需匹配你的Keycloak配置) const hasValidSession = request.cookies.get('KEYCLOAK_SESSION')?.value; if (!hasValidSession) { // 构造Keycloak登录URL,登录后跳回原路由 const loginUrl = new URL('/auth/realms/你的领域名/protocol/openid-connect/auth', 'https://你的Keycloak域名'); loginUrl.searchParams.set('client_id', '你的客户端ID'); loginUrl.searchParams.set('redirect_uri', `${request.nextUrl.origin}${request.nextUrl.pathname}`); loginUrl.searchParams.set('response_type', 'code'); loginUrl.searchParams.set('scope', 'openid'); return NextResponse.redirect(loginUrl); } return NextResponse.next(); } // 指定Middleware生效的路由 export const config = { matcher: ['/contact'], };
三、App Router:Server Component端保护
适配Next.js 13+的App Router架构,在服务器端直接检查会话。
1. 服务器端会话检查工具函数
// lib/auth.js import Keycloak from 'keycloak-connect'; import { InMemoryStore } from 'keycloak-connect/stores'; // 初始化Keycloak服务器端实例 const keycloak = new Keycloak({ store: new InMemoryStore() }, { clientId: '你的客户端ID', realm: '你的领域名', authServerUrl: 'https://你的Keycloak域名/auth', }); export async function getServerSession(req) { return new Promise((resolve) => { keycloak.authenticated(req, {}, () => { resolve(req.kauth?.grant || null); }); }); }
2. 保护目标路由页面
在app/contact/page.js中编写Server Component:
// app/contact/page.js import { redirect } from 'next/navigation'; import { getServerSession } from '../lib/auth'; export default async function ContactPage() { const session = await getServerSession(); if (!session) { // 重定向到自定义登录页或直接跳Keycloak redirect('/login'); } return <div>仅认证用户可见的联系页面</div>; }
3. 登录页触发Keycloak登录
// app/login/page.js 'use client'; import { useEffect } from 'react'; import { useAuth } from '@/contexts/AuthContext'; export default function LoginPage() { const { login } = useAuth(); useEffect(() => { login(); }, [login]); return <div>正在跳转到登录页面...</div>; }
内容的提问来源于stack exchange,提问作者NoobMaster69
相关产品推荐
相关产品推荐

