You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React对接Spring Boot Security登录接口时遇SSL协议错误求助

React + Spring Boot Security 登录接口调用报错问题

我正在尝试将React项目与Spring Boot Security后端对接,实现用户凭据验证功能:若凭据正确则跳转至/course页面,验证失败则弹出错误提示。但目前调用/login接口时出现POST https://localhost:8080/login net::ERR_SSL_PROTOCOL_ERROR及Uncaught (in promise) TypeError: Failed to fetch错误。


React登录组件代码

import React, { useState } from 'react';
import Navigation from '../NAV/nav'
import {MDBContainer,MDBCard,MDBCardBody,MDBCardImage,MDBRow,MDBCol,MDBInput} from 'mdb-react-ui-kit';
import image1 from '../images/logobck.jpeg';
import image2 from '../images/logo.png';
import './login.css'
import 'react-toastify/dist/ReactToastify.css';
import { toast } from 'react-toastify';
import { ToastContainer } from 'react-toastify';


export default function Login() {
  const [userName,setUserName] =useState("");
  const [password,setPassword] =useState("");
  const [token,setToken]=useState("");

  async function handleLogin(){
    const loginCredentials={username:userName,password:password};
    if(loginCredentials.username=='' || loginCredentials.password==''){
      toast.error("User name or password is blank");
      return
    }

    const response = await fetch("https://localhost:8080/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(loginCredentials)
    });

    if (response.ok) {
      const body=await response.json();
      const jwtToken=body.token;
      setToken(jwtToken);
      window.location.href('/courses')
    } else {
      toast.error("Invalid credentials, please try again");
    }
  }
  
  return (
    <>
    <ToastContainer/>
     <Navigation/>
    <MDBContainer className="my-5">
      <MDBCard>
        <MDBRow className='g-0'>
          <MDBCol md='6'>
            <MDBCardImage src={image1} alt="login form" className='rounded-start w-100 h-100'/>
          </MDBCol>
          <MDBCol md='6'>
            <MDBCardBody className='d-flex flex-column'>
              <div className='d-flex flex-row mt-2' style={{justifyContent:'center'}}>
                <img src={image2} style={{width:'30%',height:'30%'}}></img>
              </div>
              <h5 className="fw-normal my-4 pb-3" style={{letterSpacing: '1px'}}>Sign into your account</h5>
              <MDBInput wrapperClass='mb-4'  id='formControlLg' type='email' size="lg" placeholder='User name' value={userName} onChange={(e) =>(setUserName(e.target.value))}/>
              <MDBInput wrapperClass='mb-4'  id='formControlLg' type='password' size="lg" placeholder='Password' value={password} onChange={(e) =>(setPassword(e.target.value))}/>
              <div className='container'>
                <div className='row'>
                  <div className='col-sm'>
                    <button className='btn-login' type='button' onClick={() => handleLogin()}>Login</button>
                  </div>
                  <div className='col-sm'>
                    <a class="btn btn-outline-dark" href="/users/googleauth" role="button" style={{textTransform:'none',width:'100%'}}>
                        <img width="20px" style={{width:'30px',height:'20px'}} alt="Google sign-in" src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/512px-Google_%22G%22_Logo.svg.png" />&nbsp;&nbsp;Login with Google</a>
                  </div>
                </div>
              </div><br/>
              <a className="small  forgotpassword" href="#!">Forgot password?</a>
              <p className="mb-5 pb-lg-2" style={{color: '#393f81'}}>Don't have an account? <a href="/register" style={{color: '#393f81'}}>Register here</a></p>
              <div className='d-flex flex-row justify-content-start'>
                <a href="#!" className="small text-muted me-1">Terms of use.</a>
                <a href="#!" className="small text-muted">Privacy policy</a>
              </div>
            </MDBCardBody>
          </MDBCol>
        </MDBRow>
      </MDBCard>
    </MDBContainer>
    </>
  );
}

Spring Boot认证控制器代码

package com.project.userauthentication.auth;

import com.project.userauthentication.exceptionHandler.ErrorMessage;
import com.project.userauthentication.user.User;
import com.project.userauthentication.user.userInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;

@RestController
@RequestMapping("")
@RequiredArgsConstructor
@Data
@CrossOrigin(origins = "http://localhost:3000")
public class AuthenticationController {
    private final AuthenticationService authenticationService;
    private final UserDetailsService userDetailsService;

    @PostMapping("/register")
    public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request){
        try {
            return ResponseEntity.ok(authenticationService.register(request));
        } catch (DataIntegrityViolationException e) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body(new AuthenticationResponse("User already exist"));
        }
    }

    @PostMapping("/login")
    public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticateRequest request ){
        try{
            return ResponseEntity.ok(authenticationService.authenticate(request).getBody());
        }
        catch (BadCredentialsException e){
            return  ResponseEntity.status(HttpStatus.CONFLICT).body(new AuthenticationResponse("Invalid Credentials or not registered"));
        }
    }

    @GetMapping("/userinfo")
    public ResponseEntity<?> getUserInfo(Principal user){
        User userObj=(User) userDetailsService.loadUserByUsername(user.getName());
        userInfo user_info=new userInfo(userObj.getName(), userObj.getEmail(),userObj.getRole());
        return ResponseEntity.ok(user_info);
    }
}

Spring Boot安全配置代码

package com.project.userauthentication.config;

import jakarta.servlet.Filter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final AuthenticationProvider authenticationProvider;
    private final JwtAuthenticationFilter jwtAuthFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeHttpRequests()
                .requestMatchers("/**","/register")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

错误日志

POST https://localhost:8080/login net::ERR_SSL_PROTOCOL_ERROR
handleLogin @ login.js:26
onClick @ login.js:66
callCallback @ react-dom.development.js:4164
invokeGuardedCallbackDev @ react-dom.development.js:4213
invokeGuardedCallback @ react-dom.development.js:4277
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4291
executeDispatch @ react-dom.development.js:9041
processDispatchQueueItemsInOrder @ react-dom.development.js:9073
processDispatchQueue @ react-dom.development.js:9086
dispatchEventsForPlugins @ react-dom.development.js:9097
(anonymous) @ react-dom.development.js:9288
batchedUpdates$1 @ react-dom.development.js:26140
batchedUpdates @ react-dom.development.js:3991
dispatchEventForPluginEventSystem @ react-dom.development.js:9287
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @ react-dom.development.js:6465
dispatchEvent @ react-dom.development.js:6457
dispatchDiscreteEvent @ react-dom.development.js:6430
login.js:26          Uncaught (in promise) TypeError: Failed to fetch
    at handleLogin (login.js:26:1)
    at onClick (login.js:66:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
    at invokeGuardedCallback (react-dom.development.js:4277:1)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:1)
    at executeDispatch (react-dom.development.js:9041:1)
    at processDispatchQueueItemsInOrder (react-dom.development.js:9073:1)
    at processDispatchQueue (react-dom.development.js:9086:1)
    at dispatchEventsForPlugins (react-dom.development.js:9097:1)

问题排查与解决

1. 核心错误修复:协议不匹配

React代码中请求地址用了https://localhost:8080/login,但本地开发的Spring Boot默认运行在HTTP协议下(未配置SSL证书),导致SSL协议错误。
修改:将React的请求地址改为http://localhost:8080/login

2. 其他问题修正

  • 跳转代码错误:window.location.href('/courses')写法错误,应改为window.location.href = '/courses';如果是React项目,更推荐使用react-router-dom的useNavigate钩子实现跳转,符合SPA特性。
  • 网络异常捕获:fetch在网络错误时会直接抛出异常,当前代码未处理,需添加try-catch避免未捕获的Promise错误:
async function handleLogin(){
  const loginCredentials={username:userName,password:password};
  if(loginCredentials.username=='' || loginCredentials.password==''){
    toast.error("用户名或密码不能为空");
    return
  }

  try {
    const response = await fetch("http://localhost:8080/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(loginCredentials)
    });

    if (response.ok) {
      const body=await response.json();
      const jwtToken=body.token;
      setToken(jwtToken);
      window.location.href = '/courses';
    } else {
      toast.error("无效的凭据,请重试");
    }
  } catch (err) {
    toast.error("网络连接失败,请检查后端服务状态");
    console.error(err);
  }
}
  • 安全配置优化:requestMatchers("/**","/register")写法冗余,/**已包含/register,可改为.requestMatchers("/login", "/register", "/users/googleauth").permitAll(),精准开放无需认证的接口。

内容的提问来源于stack exchange,提问作者Manjunatha reddy Lingamdinne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 10:14:57