如何使用下一个身份验证通过自定义 Spring API 库和端点进行身份验证

IT技术 reactjs spring-boot authentication next.js next-auth
2022-07-06 02:15:15

我是 Next.js 的新手,并将我的前端从 React 移动到 Next (v4)。我已经使用 Spring 开发了一个后端,它连接到 Azure 上托管的 MySQL 数据库。该 API 在 Postman 和 React 前端上经过全面测试和功能。API 包括允许身份验证的端点。API 还会生成一个 JWT 令牌。

将前端从 React 迁移到 Next js

在尝试将前端从 React 中移出时,身份验证是面临的第一个问题。我选择尝试 next_auth,但使用用户名和密码凭据实现 next_auth 似乎存在一些问题。

使用 Spring / Java REST API 进行身份验证的下一个身份验证

上面让我问,Next auth 是否首先适合使用 Spring API 进行身份验证?或者如果自定义方法(即:标准 React)会更好?Next auth 文档似乎更喜欢使用内置方法,如 Google、Twitter 等,对自定义凭据方法的支持较少

SOF上的类似问题

在这个特定的用例上,我没有找到很多类似的问题。是我发现的唯一一个关于 Next auth 和 Spring API 的问题。这个问题没有答案。

最接近我的问题的是这个问题它说问题在于 JWT 令牌和回调的配置。我按照指示进行操作,但没有解决问题。这个问题是关于将 Next auth 连接到 Spring Boot API。这个问题通常是关于如何配置和使用凭据提供程序。

基于所有这些,我尝试了以下方法:

React 代码(包括 Postman 签名)

React 中的以下代码适用于 API。我还在 fetch 方法中提取了 Postman 签名。

const AuthForm = () => {

const emailInputRef = useRef();
const passwordInputRef = useRef();
const [isLogin, setIsLogin] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);

const authCtx = useContext(AuthContext);

const switchAuthModeHandler = () => {
setIsLogin((prevState) => !prevState);
};

const submitHandler = (event) => {
event.preventDefault();

const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;

// optional: Add validation

setIsLoading(true);

if (isLogin) {

  // Postman signature here

  var myHeaders = new Headers();
  myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
  
  var urlencoded = new URLSearchParams();
  urlencoded.append("username", enteredEmail);
  urlencoded.append("password", enteredPassword);

  var requestOptions = {
    method: 'POST',
    headers: myHeaders,
    body: urlencoded,
    redirect: 'follow'
  };

  fetch(API_LOGIN_URL, requestOptions)
    .then((res) => {
      setIsLoading(false);
      if (res.ok) {
        return res.json();
      } else {
        return res.json().then((data) => {
          let errorMessage = 'Authentication failed!';
          throw new Error(errorMessage);
        });
      }
    })
    .then((data)=> {
      authCtx.login(data.access_token);
      const processedData = JSON.stringify(data);
      console.log("Admin status "+ processedData);
     for(let i = 0; i < processedData.length; i++) {
            if(processedData.includes("ROLE_SUPER_ADMIN")) {
            console.log("Found Admin"); 
            authCtx.adminAccess(true);
          } 
          if(processedData.includes("ROLE_USER")) {
            console.log("Found User");
            break;
          }
          else {
            console.log("Not Found");
          }
    }})
    .catch((err) => {
      alert(err.message);
    });
  }
};

return (
<section className={classes.auth}>
  <h1>{isLogin ? 'Login' : 'Sign Up'}</h1>
  <form onSubmit={submitHandler}>
    <div className={classes.control}>
      <label htmlFor='email'>Your Email</label>
      <input type='email' id='email' required ref={emailInputRef} />
    </div>
    <div className={classes.control}>
      <label htmlFor='password'>Your Password</label>
      <input type='password' id='password' required ref={passwordInputRef} />
    </div>
    <div className={classes.actions}>
      {!isLoading && <button>{isLogin ? 'Login' : 'Create Account'}</button>}
      {isLoading && <p>Sending request</p>}
      <button
        type='button'
        className={classes.toggle}
        onClick={switchAuthModeHandler}
      >
        {isLogin ? 'Create new account' : 'Login with existing account'}
      </button>
    </div>
  </form>
 </section>
 );
};

export default AuthForm;

Next.js 应用程序代码

[...nextauth].js

  export default NextAuth({
  session: {
  strategy: "jwt",
  secret: process.env.SECRET,
  maxAge: 30 * 24 * 60 * 60, // 30 days
  },
  providers: [
  CredentialsProvider({
  name: 'credentials',
  credentials: {
    Username: {label: "Username", type: "text", placeholder: 'app@apptest.com'},
    Password: {label: "Password", type: "password"}
  },
  async authorize(credentials, req) {

     // extracted from Postman 
  var myHeaders = new Headers();
  myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
  
  
  var urlencoded = new URLSearchParams();
  urlencoded.append("Username", credentials.Username);
  urlencoded.append("Password", credentials.Password);

  var requestOptions = {
    method: 'POST',
    headers: myHeaders,
    body: urlencoded,d
    redirect: 'follow'
  };

const user = fetch(API_LOGIN_URL, {
  requestOptions
  })

console.log("Credentials are: " + credentials.Username)
if (user) {

// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null or false then the credentials will be rejected
return null
// You can also Reject this callback with an Error or with a URL:
// throw new Error('error message') // Redirect to error page
// throw '/path/to/redirect'        // Redirect to a URL
 }
  
 },

  })],

callbacks: {
 async session({ session, token, user }) {
  session.user.id = token.id;
  session.accessToken = token.accessToken;
  return session;
 },
async jwt({ token, user, account, profile, isNewUser }) {
if (user) {
  token.id = user.id;
}
if (account) {
  token.accessToken = account.access_token;
}
  return token;
},

}
}

)

Authform.js(登录表单)

import { useState, useRef, useContext } from 'react';
import classes from './AuthForm.module.css';
import { signIn } from 'next-auth/react';
import { API_LOGIN_URL } from '../Constants';
import { redirect } from 'next/dist/server/api-utils';

  function AuthForm () {
   const emailInputRef = useRef();
   const passwordInputRef = useRef();


 async function submitHandler (event) {
 event.preventDefault();

const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;

  const result = await signIn('credentials', {
   redirect: false,
   username: enteredEmail,
   password: enteredPassword,
   callbackUrl: `${window.location.origin}`

  });  
  console.log("The email " + enteredEmail);

    console.log("Result is: " + result.error);
 }
    
return (
  <section className={classes.auth}>
       <h1>Login</h1>
    <form onSubmit={submitHandler}>
    <div className={classes.control}>
      <label htmlFor='username'>Your Email</label>
      <input type='email' id='username' required ref={emailInputRef} />
    </div>
    <div className={classes.control}>
      <label htmlFor='password'>Your Password</label>
      <input
        type='password'
        id='password'
        required
        ref={passwordInputRef}
      />
    </div>
    <div className={classes.actions}>
      <button>Login</button>
      <button
        type='button'
        className={classes.toggle}
     >
        
      </button>
    </div>
  </form>
 </section>
 );


 }
   export default AuthForm;

错误

在前端

实际登录时,我没有收到任何错误。如果我在重新启动应用程序后刷新浏览器,我会得到:

[next-auth][warn][NO_SECRET] https://next-auth.js.org/warnings#no_secret [next-auth][error][JWT_SESSION_ERROR] https://next-auth.js.org/errors #jwt_session_error 解密操作失败 { message: '解密操作失败', stack: 'JWEDecryptionFailed: 解密操作失败\n' +

后端

登录实际上确实到达了后端(在监视 API 终端时),但它为用户名和密码值提供了 null。

主要问题

  1. Next auth 是对 Spring API 进行身份验证的正确框架,还是编写自定义身份验证方法更好(甚至坚持使用 React)?
  2. 上面的代码有什么问题?

谢谢!

1个回答

在您提到用户名的凭据中,但它的形式是电子邮件

您可以将代码更新async authorize(credentials, req)为以下内容并尝试-

const payload = {
    email: credentials.email, // make sure this field is mapped correctly
    password: credentials.password,
};

const res = await fetch(API_LOGIN_URL, {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: {
        'Content-Type': 'application/json',
        'Accept-Language': 'en-US',
    },
});

const user = await res.json();

此外,signIn方法的签名似乎不正确 -

句法

signIn('credentials', { redirect: false, password: 'password' })
signIn('email', { redirect: false, email: 'bill@fillmurray.com' })

用法

const result = await signIn('credentials', {
    headers: myHeaders,
    body: urlencoded,
   redirect: false
  });  

请更新为

const res = await signIn('credentials', {
 redirect: false,
 email: values.email,
 password: values.password,
 callbackUrl: `${window.location.origin}`, // if required
});