Laravel(API)+Next.js构建HR管理系统:认证与前后端对接全流程指导请求
嘿,我刚帮几个朋友搭过类似的Laravel+Next.js HR系统,你的需求我太懂了!结合你已经配置好Laravel Breeze的情况,我给你梳理一套标准且新手友好的对接流程,一步步来:
一、先把Laravel后端的API配置捋顺(适配Next.js)
你已经装了Breeze+Sanctum,只需要调整几个关键配置让它支持Next.js的跨域请求:
- 环境变量配置
打开.env文件,更新这两个参数(假设Next.js跑在localhost:3000,Laravel在localhost:8000):
SESSION_DOMAIN=localhost SANCTUM_STATEFUL_DOMAINS=localhost:3000
- CORS跨域配置
修改config/cors.php,确保允许携带凭证、开放必要路径:
return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_origins' => ['http://localhost:3000'], 'allowed_origins_patterns' => [], 'allowed_methods' => ['*'], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, // 这个必须开,否则cookie传不过去 ];
- 确认API路由的认证中间件
Laravel Breeze已经帮你生成了routes/api.php的认证组,你只需要把HR系统的私有接口(比如员工管理、部门管理)放进这个组里:
Route::middleware('auth:sanctum')->group(function () { Route::get('/user', function (Request $request) { return $request->user(); }); // 把你的HR接口加在这里 Route::apiResource('employees', EmployeeController::class); Route::apiResource('departments', DepartmentController::class); });
二、Next.js前端的新手友好型项目结构
给你推荐一个清晰且易维护的结构,避免后期代码混乱:
app/ - auth/ - login/ - page.jsx # 登录页面 - dashboard/ - page.jsx # HR系统后台首页 - layout.jsx # 后台专属布局(侧边栏、导航栏) - layout.jsx # 全局根布局 - page.jsx # 未登录时的首页 lib/ - api.js # 封装所有API请求,统一处理token、错误 - auth.js # 认证工具函数:登录、登出、获取用户信息 middleware.js # Next.js路由中间件,管控未登录跳转
三、Token认证全流程(登录、登出、过期处理)
因为你用的是Laravel Breeze的SPA模式,优先用Sanctum的Cookie认证(比存token到localStorage安全10倍),流程如下:
1. 登录流程
Next.js前端需要先获取Sanctum的CSRF Cookie,再发送登录请求:
先在lib/api.js封装通用请求和登录函数:
// 封装带认证的API请求 export async function apiRequest(url, options = {}) { const baseUrl = process.env.NEXT_PUBLIC_API_URL; // 根目录.env里配置这个变量 const res = await fetch(`${baseUrl}${url}`, { credentials: 'include', // 自动携带认证Cookie headers: { 'Content-Type': 'application/json', ...options.headers, }, ...options, }); // 处理登录过期/未授权 if (res.status === 401) { await logoutUser(); window.location.href = '/auth/login'; throw new Error('登录已过期,请重新登录'); } if (!res.ok) { const error = await res.json(); throw new Error(error.message || '请求失败'); } return res.json(); } // 登录函数 export async function loginUser(email, password) { // 先获取Sanctum的CSRF Cookie await fetch(`${process.env.NEXT_PUBLIC_API_URL}/sanctum/csrf-cookie`, { credentials: 'include', }); // 发送登录请求到Laravel Breeze的API接口 const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/login`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }), }); if (!res.ok) { const error = await res.json(); throw new Error(error.message || '登录失败'); } return res.json(); } // 登出函数 export async function logoutUser() { await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/logout`, { method: 'POST', credentials: 'include', }); }
然后在登录页面app/auth/login/page.jsx调用这个函数:
'use client'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { loginUser } from '@/lib/api'; export default function LoginPage() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [errorMsg, setErrorMsg] = useState(''); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); setErrorMsg(''); try { await loginUser(email, password); router.push('/dashboard'); // 登录成功跳后台 } catch (err) { setErrorMsg(err.message); } }; return ( <div className="max-w-md mx-auto mt-16 p-6 border rounded-lg shadow"> <h2 className="text-2xl font-bold mb-6 text-center">HR管理系统登录</h2> <form onSubmit={handleSubmit}> <div className="mb-4"> <label className="block text-sm font-medium mb-1">邮箱</label> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className="w-full p-2 border rounded-md" required /> </div> <div className="mb-4"> <label className="block text-sm font-medium mb-1">密码</label> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} className="w-full p-2 border rounded-md" required /> </div> {errorMsg && <p className="text-red-500 text-sm mb-4">{errorMsg}</p>} <button type="submit" className="w-full bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600 transition" > 登录 </button> </form> </div> ); }
2. 登出流程
在后台布局或者导航栏里加个登出按钮,调用logoutUser函数即可:
import { logoutUser } from '@/lib/api'; import { useRouter } from 'next/navigation'; function LogoutButton() { const router = useRouter(); const handleLogout = async () => { await logoutUser(); router.push('/auth/login'); }; return <button onClick={handleLogout}>退出登录</button>; }
3. 自动处理登录过期
上面的apiRequest函数已经帮你做了:只要后端返回401未授权,就自动登出并跳转到登录页,不用手动在每个页面判断。
四、前后端中间件:管控访问权限
1. Laravel后端:保护私有API
刚才已经说了,把需要登录才能访问的API放进auth:sanctum中间件组里,这样未登录的请求会直接返回401。
2. Next.js前端:管控页面访问
用Next.js的middleware.js实现:未登录用户不能进后台,已登录用户不能进登录页。
先在lib/auth.js写个获取当前用户的函数:
import { apiRequest } from './api'; export async function getCurrentUser() { try { const user = await apiRequest('/api/user'); return user; } catch (err) { return null; } }
然后创建middleware.js:
import { NextResponse } from 'next/server'; import { getCurrentUser } from './lib/auth'; export async function middleware(request) { const user = await getCurrentUser(); const isAuthPage = request.nextUrl.pathname.startsWith('/auth'); const isDashboardPage = request.nextUrl.pathname.startsWith('/dashboard'); // 未登录且访问后台,跳登录页 if (!user && isDashboardPage) { return NextResponse.redirect(new URL('/auth/login', request.url)); } // 已登录且访问登录页,跳后台 if (user && isAuthPage) { return NextResponse.redirect(new URL('/dashboard', request.url)); } return NextResponse.next(); } // 指定要应用中间件的路由 export const config = { matcher: ['/dashboard/:path*', '/auth/:path*'], };
五、Next.js的认证状态持久化(安全优先)
因为用了Sanctum的Cookie认证,不需要把token存在localStorage/sessionStorage(不安全,容易被XSS攻击),只需要用React Context管理全局用户状态,方便页面快速获取用户信息:
- 在
app目录下创建AuthProvider.jsx:
'use client'; import { createContext, useContext, useEffect, useState } from 'react'; import { getCurrentUser } from '@/lib/auth'; import { loginUser, logoutUser } from '@/lib/api'; const AuthContext = createContext(); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // 页面加载时自动获取当前用户 useEffect(() => { async function fetchUser() { const currentUser = await getCurrentUser(); setUser(currentUser); setLoading(false); } fetchUser(); }, []); // 封装登录方法,成功后更新用户状态 const handleLogin = async (email, password) => { const userData = await loginUser(email, password); setUser(userData.user); }; // 封装登出方法,成功后清空用户状态 const handleLogout = async () => { await logoutUser(); setUser(null); }; return ( <AuthContext.Provider value={{ user, loading, login: handleLogin, logout: handleLogout }}> {!loading && children} </AuthContext.Provider> ); } // 自定义Hook,方便页面调用 export function useAuth() { return useContext(AuthContext); }
- 在全局根布局
app/layout.jsx里包裹所有页面:
import { AuthProvider } from './AuthProvider'; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body> <AuthProvider>{children}</AuthProvider> </body> </html> ); }
这样你在任何页面都可以用useAuth()快速获取用户状态、调用登录登出:
import { useAuth } from '@/app/AuthProvider'; function DashboardPage() { const { user } = useAuth(); return <h1>欢迎回来,{user.name}!</h1>; }
最后总结
这套流程完全适配你已有的Laravel Breeze配置,核心是用Sanctum的Cookie认证替代传统的token存储,既安全又省掉了手动管理token的麻烦。Next.js的结构清晰,新手也能快速上手,后续加HR功能只需要往对应目录里加页面和API就行。
如果后续要支持移动端APP,再扩展Sanctum的个人访问令牌功能就行,现在的SPA场景用Cookie认证是最优解。有细节问题随时问!




