React+Spring Boot:JWT授权头始终为Undefined的令牌处理问题
问题概述
开发基于React前端和Spring Boot后端的全栈项目时,前端调用受保护API接口时,请求的Authorization头始终为"Bearer undefined",后端抛出JWT格式异常。
问题详情
使用Axios发起HTTP请求,通过React Context(AuthProvider)管理认证并存储JWT令牌。后端已确认登录成功后能生成并返回JWT令牌,但前端未正确获取或设置令牌,导致Authorization头出现"Bearer undefined"。后端日志报错:JWT strings must contain exactly 2 period characters. Found: 0.
环境配置
- 前端:React + Axios,用useContext管理认证,通过useEffect全局设置Axios请求头
- 后端:Spring Boot,包含JwtController和JwtAuthenticationFilter用于认证
相关代码
前端AuthProvider(JWT令牌管理上下文)
import axios from 'axios'; import { createContext, useContext, useMemo, useState, useEffect } from 'react'; const AuthContext = createContext(); const AuthProvider = ({ children }) => { const [token, setToken_] = useState(() => { return localStorage.getItem("token") || null; }); const setToken = (newToken) => { if (newToken) { localStorage.setItem('token', newToken); setToken_(newToken); } else { localStorage.removeItem('token'); setToken_(null); } }; useEffect(() => { if (token) { console.log("Setting default Authorization header:", token); axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; } else { delete axios.defaults.headers.common["Authorization"]; } }, [token]); const contextValue = useMemo(() => ({ token, setToken }), [token]); return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>; }; export const useAuth = () => useContext(AuthContext); export default AuthProvider;
前端LoginForm(用户登录组件)
import React, { useState } from 'react'; import axios from 'axios'; import { useAuth } from "../provider/authProvider"; import { useNavigate } from 'react-router-dom'; function LoginForm() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const navigate = useNavigate(); const { setToken } = useAuth(); const handleSubmit = async (e) => { e.preventDefault(); try { const response = await axios.post('http://localhost:8080/auth/login', { username, password }, { headers: { 'Content-Type': 'application/json' } }); console.log("Full response:", response); console.log("Response data:", response.data); const { jwt } = response.data; if (jwt) { setToken(jwt); console.log("Token set:", jwt); navigate('/profile'); } else { console.error("JWT token is undefined or empty. Check backend response."); } } catch (error) { console.error('Error logging in!', error); if (error.response) { console.error('Error details:', error.response.data); } } }; return ( <form onSubmit={handleSubmit}> <h2>Login</h2> <label> Username: <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} /> </label> <label> Password: <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </label> <button type="submit">Login</button> </form> ); } export default LoginForm;
后端Spring Boot控制器
@RestController @RequestMapping("/auth") public class JwtController { @Autowired private JwtUtil jwtUtil; @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<?> authenticate(@RequestBody AuthRequest authRequest) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); } catch (BadCredentialsException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials"); } final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); final String jwt = jwtUtil.generateToken(userDetails); System.out.println("Generated JWT token: " + jwt); // Debug return ResponseEntity.ok(new AuthenticationResponse(jwt)); } }
后端日志
Authorization header: Bearer undefined Extracted JWT token from header: undefined JWT strings must contain exactly 2 period characters. Found: 0
求助问题
- 为何后端已生成JWT令牌,但前端获取到的令牌仍为undefined?
- localStorage、useEffect或Axios默认请求头是否存在可能导致竞态条件的已知问题?
- 前端存储或设置令牌的方式存在哪些问题?
已尝试的解决方法
- 调试后端确认令牌生成正常
- 在前端打印后端响应以验证是否接收到令牌
- 将令牌存储到localStorage并设置Axios全局请求头
- 检查Spring Boot的CORS配置,已允许所有来源和请求头
预期与实际结果
预期结果
- 登录成功后,JWT令牌应被设置到后续请求的Authorization头中
- 后端应接收到有效的JWT令牌而非undefined
实际结果
- 登录后所有请求的Authorization头均为"Bearer undefined"
- 后端抛出JWT令牌格式错误或缺失的异常
排查与解决思路
- 字段名不匹配:检查后端
AuthenticationResponse类的字段名是否为jwt,前端通过response.data.jwt获取令牌,若后端返回字段为token或其他名称,会直接导致jwt为undefined。 - 响应结构验证:查看前端控制台打印的
response.data具体内容,确认后端返回的JSON是否直接包含jwt字段,而非嵌套在其他对象中。 - 竞态条件处理:登录后调用
navigate('/profile')可能在setToken状态更新完成前跳转,导致/profile页面发起请求时令牌未完成设置。可通过useEffect监听token变化,待令牌存在后再执行跳转。 - 全局请求头设置校验:检查前端控制台是否打印
Setting default Authorization header:及对应令牌值,若未打印,说明token状态未正确更新。 - localStorage读取检查:页面刷新时,验证
localStorage.getItem("token")是否能正确获取到令牌,排查存储时的编码或格式问题。
内容的提问来源于stack exchange,提问作者Shafi Chowdhury
相关产品推荐
相关产品推荐

