
lynn1286
V1
2022/04/10阅读:1268主题:默认主题
Nextjs 登录实现
文末有地址,可以结合文章观看,文中的接口均本地 mock 😁
之前写了一篇文章是 使用 next-auth 实现登录持久化,这几天想了下如何不使用第三方库自己实现一套登录逻辑。
思考
SSR 框架下的登录逻辑跟纯客户端渲染的项目不太一样, 前者需要考虑如何在两个端下都获取到登录凭证,而后者没有这个考虑。
不了解 SSR 框架的同学可能会有疑问, 为什么SSR下会不一样? 那是因为在 SSR 框架下 , 分服务端渲染和客户端渲染, 而服务端渲染的环境是处于 node 环境,客户端是处于 浏览器(window) 环境, 因为环境的不一样,我们在服务端下无法访问 window 对象, 也就意味着我们不能将 登录凭证存储到 local storage 中(在纯客户端渲染的情况下我们都这么干),所以在 SSR 下我们需要考虑这个问题,因为在项目中很可能需要在客户端下获取登录凭证,也可能会在服务端下获取登录凭证。
实现
1. createContext 创建一个全局对象
// lib/context.ts
import React from 'react'
export interface IMyContextProps {
// 是否登录标记
isLoggedIn: boolean
// 用户信息
user: {
name: string
email: string
} | null
// useState 创建的函数 , 用于设置用户信息
setUser?: any
}
const MyContext = React.createContext<IMyContextProps>({
isLoggedIn: false,
user: null,
})
export default MyContext
2. 在 _app.tsx 中使用context provider
在 _app.tsx 中实现如下逻辑:
// pages/_app.tsx
import Head from 'next/head'
import Cookie from 'js-cookie'
import MyContext from '../lib/context'
import { useEffect, useState } from 'react'
import '@styles/globals.css'
import '@styles/antd.less'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
// 1. 创建 state ,保存用户信息
const [user, setUser] = useState<{ name: string; email: string } | null>(null)
useEffect(() => {
// 2. 从 cookie 中获取 登录凭证
const jwt = Cookie.get('jwt')
// 3. 如果有登录凭证, 发送请求给后端服务获取用户信息
if (jwt) {
fetch(`${process.env.NEXT_PUBLIC_API_URL}/me`, {
headers: {
Authorization: `Bearer ${jwt}`,
},
}).then(async (res) => {
if (!res.ok) {
Cookie.remove('jwt') // 如果请求user 失败, 清理 cookie ,这里可以根据请求的状态码来判断处理
setUser(null)
}
const user = await res.json()
setUser(user)
})
}
}, [])
return (
<>
<Head>
<title>React-Nextjs-Template</title>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
</Head>
// 4. 使用 provide ,将 prop 传入
<MyContext.Provider
value={{
user: user,
setUser,
// 注意这里,isLoggedIn的值是根据 user 是否为空来决定的,
// 所以,我们要将 setUser 函数一并传入 context ,这样做是为了在其他组件中使用这个方法触发 isLoggedIn 的值改变
isLoggedIn: !!user,
}}
>
<Component {...pageProps} />
</MyContext.Provider>
</>
)
}
export default MyApp
3. 登录页面
删除了一些代码,留下核心逻辑
// pages/auth/login.tsx
import Cookie from 'js-cookie'
import Head from 'next/head'
import React, { useState, useEffect, useContext } from 'react'
import MyContext from '@lib/context'
import { useRouter } from 'next/router'
import { login } from '@lib/auth'
import Link from 'next/link'
import { getUser } from '@lib/user' // 登录
export default function Login() {
// Context 中获取数据
const { isLoggedIn, setUser } = useContext(MyContext)
const router = useRouter()
// 登录
const signIn = async () => {
...
const reg: any = await login(email, password)
// 获取到token
if (reg.accessToken) {
// 拿到token 获取用户信息
const user = await getUser()
if (user) setUser(user)
router.push('/')
} else {
setErrors({ server: reg?.error?.message || 'Error from server' })
}
}
useEffect(() => {
if (isLoggedIn) {
router.push('/home/dashboard')
}
}, [isLoggedIn])
return (
<div>
...
</div>
)
}
// lib/user.ts
import Cookie from 'js-cookie'
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'
export const getUser = async () => {
const token = Cookie.get('jwt')
try {
let response = await fetch(`${API_URL}/me`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
})
response = await response.json()
return response
} catch (e) {
return { error: 'An error occured' }
}
}
// lib/auth.ts
import Cookie from 'js-cookie'
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'
export interface IResponse extends Response {
accessToken?: string
}
export const login = async (identifier: string, password: string) => {
try {
let response: IResponse = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
body: JSON.stringify({ email: identifier, password }),
headers: {
'Content-Type': 'application/json',
},
})
response = await response.json()
if (response.accessToken) {
Cookie.set('jwt', response.accessToken)
}
return response
} catch (e) {
return { error: '登录失败,请稍后重试' }
}
}
以上三个步骤完成了登录的逻辑处理,包括刷新页面还是会拿到用户的信息。
pages 下中间件拦截
import type { NextFetchEvent, NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
// NextResponse 在中间件中提供了四种方法。
// - redirect():redirect 方法将请求从一条路由重定向到另一条路由。
// - rewrite():重写你的退出响应。
// - next():next 方法将中间件链从一个中间件延续到另一个中间件。
// - json(): json 方法返回 JSON 响应或数据
export function middleware(req: NextRequest, ev: NextFetchEvent) {
// 请求头中可以读取到 cookie
if (req.cookies) {
return NextResponse.next()
}
// 如果没有cookie 则重定向回 login 页面
return NextResponse.redirect('/login')
}
api 下中间件拦截
// This is an example of how to access a session from an API route
import type { NextApiRequest, NextApiResponse } from 'next'
import Cookie from 'js-cookie'
export default async (req: NextApiRequest, res: NextApiResponse) => {
// 同样可以在请求头中读取 cookie
if (req.cookies) {
res.send('401 forbidden')
}
res.status(200).send('请求成功')
}
总结
-
在 _app.tsx 中处理 每次刷新页面就去读取 token 请求 user 接口获取user信息存入 context ; -
登录页面登录成功后拿到token调用user接口设置 context
如果文章能够帮到你,请给我点个 👍
作者介绍

lynn1286
V1