next.js通过中间件进行路由鉴权

1,824 阅读3分钟

由于import { cookies } from 'next/headers' 的在client端无法设置cookie,所有需要安装js-cookie在client端设置cookie,目的是在中间件中取到cookie里的token

1.登录时存储token

我把登录逻辑放在zustand了,路径也是vue写习惯了,照着vue来的。

1.1 cookie方法封装

// D:\study\next-js-learning\src\utils\cookie.ts
import Cookies from "js-cookie";
export async function createCookie({
  name,
  value,
}: {
  name: string;
  value: string;
}) {
  Cookies.set(
    name,
    value,
    // path: "/",
  );
}

export async function deleteCookie(name: string) {
  return Cookies.remove(name);
}

export async function getCookie(name: string) {
  return Cookies.get(name);
}

1.2 localstorage方法封装

// D:\study\next-js-learning\src\utils\storage.ts
'use client'
// localStorage封装
const localStorage = typeof window !== `undefined` ? window.localStorage : null
const localStorageUtil = {
  /**
   * 设置 localStorage
   * @param key
   * @param value
   */
  set(key: string, value: any) {
    localStorage?.setItem(key, JSON.stringify(value));
  },
  /**
   * 获取 localStorage
   * @param key
   */
  get(key: string) {
    const value = localStorage?.getItem(key);
    if (value) {
      try {
        return JSON.parse(value);
      } catch (e) {
        return value;
      }
    }
    return null;
  },
  /**
   * 删除 localStorage
   * @param key
   */
  remove(key: string) {
    localStorage?.removeItem(key);
  },
  /**
   * 清空 localStorage
   */
  clear() {
    localStorage?.clear();
  },
};
export default localStorageUtil;

1.3 登录逻辑封装

// D:\study\next-js-learning\src\store\modules\user.ts
"use client";
import { create } from 'zustand'
import type { userInfo as IUserInfo } from "@/api/user/type";
import { userInfo } from "@/api/user";
import { login } from "@/api/user/login";
import storage from "@/utils/storage";
import { IUserLogin } from "@/api/user/type";
import { createCookie, deleteCookie } from "@/utils/cookie";
export const useUserStore = create<{
  userInfo: IUserInfo;
  token: string | null;
  setToken: (token: string) => void;
  setUser: (userInfo: IUserInfo) => void;
  logout: () => void;
  getUserInfo: () => void;
  userLogin: (data: IUserLogin) => void;
  isDark: boolean;
  setTheme: (isDark: boolean) => void;
}>()((set, get) => ({
  userInfo: storage.get("userInfo") || ({} as IUserInfo),
  token: "",
  isDark: storage.get("isDark") || false,
  setToken: (token) => {
    set({ token });
    storage.set("token", token);
    createCookie({ name: "token", value: token });
  },
  setUser: (userInfo: IUserInfo) => set({ userInfo }),
  logout: () => {
    deleteCookie("token");
    storage.remove("token");
    storage.remove("userInfo");
    set({ userInfo: {} as IUserInfo, token: "" });
    location.href = "/login";
  },
  userLogin: async (values: IUserLogin) => {
    // 设置主题
    const isDark = storage.get("isDark");
    if (isDark) {
      get().setTheme(isDark);
    }
    try {
      deleteCookie("token");
      storage.remove("token");
      storage.remove("userInfo");
      set({ userInfo: {} as IUserInfo, token: "" });
      const res = await login(values);
      console.log("userLogin try😊===》");
      get().setToken(res.data);
      await get().getUserInfo();
      return res;
    } catch (error) {
      console.log("userLogin catch😊===》", error);
      throw error;
    }
  },
  getUserInfo: async () => {
    console.log("getUserInfo😊===》");
    const res = await userInfo();
    storage.set("userInfo", res.data);
    const { username, id } = res.data!;
    createCookie({ name: "userInfo", value: JSON.stringify({ username, id }) });
    set({ userInfo: res.data });
    return res;
  },
  setTheme: (isDark) => {
    set({ isDark });
    storage.set("isDark", isDark);
  },
}));

2.中间件

登录逻辑之中,把token存在了cookie里面,在中间件这里就能拿到了。
这里的处理逻辑就类似vue项目中的permission.ts了。
没token就跳到登录,有token就允许跳转。
访问白名单地址就放行,访问个别需要权限的页面再判断权限 权限在登录时可以存到cookie,也可以在middleware.ts中,通过fetch请求接口获取。
这里没法用axios,只能用fetch

// D:\study\next-js-learning\src\middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// import { userInfo as userInfoAPI } from "@/api/user";
// This function can be marked `async` if using `await` inside

// const test = (token) => {
//   return new Promise((resolve, reject) => {
//     fetch('http://127.0.0.1:3000/api/v1/user/profile',{
//       headers: {
//         'Authorization': `Bearer ${token}`
//       }
//     }).then((res) => {
//       res.json().then((data) => {
//         console.log('--🚀🚀🚀🚀🚀------middleware.ts---注释所在行数11----😊===》', data)
//         resolve(data);
//       })
//     }).catch((err) => {
//         console.log('--🚀🚀🚀🚀🚀------middleware.ts---注释所在行数12----😊===》', err)
//         reject(err);
//     });
//   });
// }

export async function middleware(request: NextRequest) {
  /**
   * 中间件-权限控制
   * 0 如果跳转路径是白名单,继续执行。
   * 1. 获取cookie中的token,如果没有token,跳转到登录页
   * 2. 如果有token,获取用户权限,如果没有权限,跳转到403页面。
   * 3. 如果有权限,继续执行。
   * 4. 如果是登录页,跳转到首页。
   */
  const { pathname } = request.nextUrl;
  const whiteList = ["/login", "/403", "/404", "/500"];
  const isWhite = whiteList.includes(pathname);
  if (isWhite) {
    return NextResponse.next();
  } else {
    const token = request.cookies.get("token" as any)?.value;
    // const userInfo = request.cookies.get("userInfo" as any)?.value;
    // console.log('--🚀🚀🚀🚀🚀------middleware.ts---注释所在行数37----😊===》', token)
    // if (userInfo) {
    //   console.log('--🚀🚀🚀🚀🚀--middleware.ts---注释所在行数25----😊===》', JSON.parse(userInfo as any));
    // }
    // if(true){
    //   await test(token);
    // }
    if (!token) {
      return NextResponse.redirect(new URL("/login", request.url));
    } else {
      return NextResponse.next();
    }
  }
}

// See "Matching Paths" below to learn more
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    "/((?!api|_next/static|_next/image|favicon.ico).*)",
  ],
};