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" /> 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
相关产品推荐
相关产品推荐

