lynn1286/next-template-app

问题:关于自定义登陆界面

Closed this issue · 2 comments

大佬,询问一下。关于自定义登陆界面:当我点击 /user/login路径下的登陆功能时,在 [...nextauth].js文件中并没有任何打印内容,看起来没有反应,请问应该如何修改呢?
我的[...nextauth].js文件如下

import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";

export default NextAuth({
  providers: [
    CredentialsProvider({
      name: "login",
      // credentials: {
      //   username: {
      //     label: "账户",
      //     type: "text",
      //     placeholder: "请输入账户",
      //   },
      //   password: {
      //     label: "密码",
      //     type: "password",
      //     placeholder: "请输入密码",
      //   },
      // },
      async authorize(credentials, req) {
        console.log(credentials);
        console.log(req);
      },
    }),
  ],
  session: {
    strategy: "jwt",
  },
  pages: {
    signIn: "/user/login",
  },
  callbacks: {
    // 每次调用 getSession() 、useSession() 的时候 都会触发并将 token 存入 user 中
    async session({ session, token }) {
      session.user.accessToken = token.accessToken;
      session.user.refreshToken = token.refreshToken;
      session.error = token.error; // 用于处理token 失效

      return session;
    },
  },
  debug: process.env.NODE_ENV === "development",
});

我的 /user/login内容如下:

import React from "react";
import { Form, Input, Button } from "antd";
import { UserOutlined, LockOutlined } from "@ant-design/icons";
import { getCsrfToken } from "next-auth/react";
import styles from "./index.module.less";

function Login(props) {
  const [form] = Form.useForm();

  const handleFormSubmit = (values) => {
    fetch("/api/auth/callback/credentials", {
      method: "POST",
      body: JSON.stringify(values),
    });
  };

  return (
    <div className={styles.container}>
      <div className={styles.login_content}>
        <div className={styles.header}>
          <div>登陆</div>
        </div>
        <Form
          form={form}
          // onFinish={handleFormSubmit}
          action="/api/auth/callback/credentials"
          initialValues={{
            csrfToken: props.csrfToken,
          }}
        >
          <Form.Item name="csrfToken" hidden>
            <Input />
          </Form.Item>
          <Form.Item
            label=""
            name="username"
            rules={[{ required: true, message: "请输入登陆账户" }]}
          >
            <Input
              size="large"
              placeholder="请输入登陆账户"
              prefix={<UserOutlined />}
              autoComplete="off"
            />
          </Form.Item>
          <Form.Item
            label=""
            name="password"
            rules={[{ required: true, message: "请输入登陆密码" }]}
          >
            <Input.Password
              placeholder="请输入登陆密码"
              prefix={<LockOutlined />}
              size="large"
            />
          </Form.Item>
          <Form.Item>
            <Button
              type="primary"
              block
              size="large"
              onClick={() => form.submit()}
            >
              登陆
            </Button>
          </Form.Item>
        </Form>
      </div>
    </div>
  );
}

export async function getServerSideProps(context) {
  return {
    props: {
      csrfToken: await getCsrfToken(context),
    },
  };
}
export default Login;

对于_app.js文件

import { SessionProvider } from "next-auth/react";
import "../styles/globals.less";

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

export default MyApp;

你好,请问你是如何解决的 ?

@dingminghui

// [...nextauth].js 文件
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";

const providers = [
  CredentialsProvider({
    id: "credentials",
    name: "Credentials",
    // 若是不自定义登陆界面,则默认以此内容渲染相应的表单
    credentials: {
      username: {
        label: "账户",
        type: "text",
        placeholder: "请输入登陆账户",
      },
      password: {
        label: "密码",
        type: "password",
        placeholder: "请输入登陆密码",
      },
    },
    // 登陆验证逻辑,返回用户信息表示登陆成功,返回 null 或 false 表示登陆失败
    async authorize(credentials, req) {
      try {
        const result = await fetch(
          `${process.env.APP_URL}/api/v1/users/login`,
          {
            method: "POST",
            body: JSON.stringify(credentials),
            headers: { "Content-Type": "application/json" },
          }
        );
        const resultData = await result.json();
        const { user, access_token } = resultData.data;
        return {
          ...user,
          access_token,
        };
      } catch (error) {
        return null;
      }
    },
  }),
];

export default NextAuth({
  providers,
  debug: process.env.NODE_ENV === "development",
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: "jwt",
    maxAge: 2 * 60 * 60,
  },
  jwt: {
    maxAge: 2 * 60 * 60,
  },
  pages: {
    signIn: "/login", // 自定义登陆界面
  },
  theme: {
    colorScheme: "light",
  },
  /**
   * 回调
   * jwt:每当创建 JSON WEB 令牌(即登录时)或更新(即每当在客户端访问会话时)时都会调用此回调,返回的值将被加密并存储在cookie中。
   * session:每当检查会话时(调用getSession()、useSession()、/api/auth/session等方法时),都会调用此会话回调。想使通过jwt()回调添加到令牌中的某些内容可用,则必须在此处显式转发以使其对客户端可用。
   * 回调的优先级:jwt > session
   */
  callbacks: {
    async jwt({ token, user, account }) {
      // 在登陆时判断是否是自定义登录的方式,并将用户信息保存到next-auth生成的token中,(因为next-auth最终提供的用户信息很少,不能满足需要,因此需要我们自己通过传递设置)
      if (account && account.type === "credentials" && user) {
        token.user = user;
        token.accessToken = user.access_token;
      }
      return token;
    },
    async session({ session, token }) {
      // 自定义会话中的user(因为默认的会话中的user信息不能满足我们的需求)
      session.user = token.user;
      session.accessToken = token.accessToken;
      return session;
    },
  },
});
// _app.js文件
import "../styles/globals.less";
import { SessionProvider, useSession } from "next-auth/react";

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      {/* 当页面存在auth属性时,表示需要登陆后才能访问 */}
      {Component.auth ? (
        <Auth>
          <Component {...pageProps} />
        </Auth>
      ) : (
        <Component {...pageProps} />
      )}
    </SessionProvider>
  );
}

export default MyApp;

// 权限
function Auth({ children }) {
  const { data: session } = useSession({ required: true });
  const user = session?.user;
  // 已经登陆时,直接进入访问页面
  if (user) {
    return children;
  }
  // 当没有登陆时,展示加载内容(然后会自动定向到登陆页面)
  return <div>...loading</div>;
}
// /pages/login.js 文件
import React, { useEffect } from "react";
import { Form, Input, Button, message } from "antd";
import { UserOutlined, LockOutlined } from "@ant-design/icons";
import { useRouter } from "next/router";
import { getCsrfToken, signIn } from "next-auth/react";
import styles from "@/styles/Login.module.less";

function Login({ csrfToken }) {
  const [form] = Form.useForm();
  const router = useRouter();

  //   表单提交
  const handleFormSubmit = async (values) => {
    signIn("credentials", { ...values, callbackUrl: router.query.callbackUrl });
  };

  useEffect(() => {
    // 因为next-auth处于安全考虑,不主动提供错误信息的详细内容传递。因此这里自己判断
    if (router.query.error && router.query.error === "CredentialsSignin") {
      message.error("很抱歉,用户账号错误,或您输入的密码错误。忘记密码了?");
    }
  }, [router]);

  return (
    <div className={styles.container}>
      <div className={styles.login_bg} />
      <div className={styles.login_form}>
        <div className={styles.form_content}>
          <div className={styles.header}>
            <div className={styles.title}>欢迎,登录</div>
            <div className={styles.sub_title}>请使用集团 OA 账户登录</div>
          </div>
          <Form
            form={form}
            initialValues={csrfToken}
            onFinish={handleFormSubmit}
          >
            <Form.Item name="csrfToken" hidden>
              <Input size="large" />
            </Form.Item>
            <Form.Item
              name="username"
              label=""
              rules={[{ required: true, message: "请输入登陆账户" }]}
            >
              <Input
                prefix={<UserOutlined />}
                size="large"
                autoComplete="off"
                placeholder="请输入登陆账户"
              />
            </Form.Item>
            <Form.Item
              name="password"
              label=""
              rules={[{ required: true, message: "请输入登陆密码" }]}
            >
              <Input.Password
                prefix={<LockOutlined />}
                size="large"
                placeholder="请输入登陆密码"
              />
            </Form.Item>
            <Form.Item>
              <Button
                block
                type="primary"
                size="large"
                onClick={() => form.submit()}
                className={styles.login_btn}
              >
                登陆
              </Button>
            </Form.Item>
          </Form>
        </div>
      </div>
    </div>
  );
}

export default Login;

export async function getServerSideProps(context) {
  return {
    props: {
      csrfToken: await getCsrfToken(context),
    },
  };
}
import Head from "next/head";
import styles from "../styles/Home.module.less";
import { useSession, signIn, signOut } from "next-auth/react";
import { Button } from "antd";

export default function Home() {
  const { data: session } = useSession();
  const user = session?.user;
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1>HOME PAGE</h1>
        {user ? (
          <Button onClick={() => signOut()}>退出登陆</Button>
        ) : (
          <Button onClick={() => signIn()}>登陆</Button>
        )}
        {user && <div>欢迎您,{user.name}</div>}
      </main>
    </div>
  );
}
//  例如 admin页面
import React from "react";
import { getSession } from "next-auth/react";
import { GET } from "../service/axios";

function Admin() {
  return (
    <div>
      <h1>欢迎您,管理员</h1>
    </div>
  );
}

export default Admin;

export async function getServerSideProps(context) {
  const session = await getSession(context);
  let users;
  try {
    users = await GET(
      "/api/v1/user",
      {},
      { headers: { authorization: `Bearer ${session.user.accessToken}` } }
    );
    console.log(users);
  } catch (error) {
    console.log(error);
  }
  return {
    props: {},
  };
}

// 当页面设置auth时,表示需要登陆后才能访问
Admin.auth = true;