Network Tab可见JWT Token,但JavaScript无法获取响应Authorization头
问题描述
通过React的JavaScript fetch API向Spring Boot后端发送登录请求,浏览器Network Tab显示响应包含带JWT Token的Authorization头,但在代码中调用res.headers.get('Authorization')时返回null,响应Body数据正常。
相关代码
前端fetch API代码
import { SIGN_IN, SIGN_UP } from "./ActionType"; export const signinAction = (data) => async (dispatch) => { try { const res = await fetch("http://localhost:5050/signin", { method: "GET", headers: { Authorization: "Basic " + btoa(data.email + ":" + data.password), }, }); console.log(res); const user = await res.json(); console.log("signin user: ", user); const token = res.headers.get("Authorization"); localStorage.setItem("token", token); dispatch({ type: SIGN_IN, payload: token }); console.log("signin token: ", token); } catch (error) { console.log(error); } }; export const signupAction = (data) => async (dispatch) => { try { const res = await fetch("http://localhost:5050/signup", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: data.email, name: data.name, userName: data.userName, password: data.password, }), }); const user = await res.json(); console.log("signup user: ", user); dispatch({ type: SIGN_UP, payload: user }); } catch (error) { console.log(error); } };
Spring Boot登录接口代码
package com.vinitpk.instagramapi.instagram.controller; import com.vinitpk.instagramapi.instagram.exception.UserException; import com.vinitpk.instagramapi.instagram.model.User; import com.vinitpk.instagramapi.instagram.repository.UserRepository; import com.vinitpk.instagramapi.instagram.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.Optional; @RestController public class AuthController { @Autowired private UserService userService; @Autowired private UserRepository userRepository; @PostMapping("/signup") public ResponseEntity<User> registerUserHandler(@RequestBody User user) throws UserException { User createdUser = userService.registerUser(user); System.out.println(createdUser.getUserName()); return new ResponseEntity<User>(createdUser, HttpStatus.OK); } @GetMapping("/signin") public ResponseEntity<User> signinUserHandler(Authentication authentication ) throws BadCredentialsException { Optional<User> optionalUser = userRepository.findByEmail(authentication.getName()); System.out.println(optionalUser.get()); if(optionalUser.isPresent()){ return new ResponseEntity<User>(optionalUser.get(),HttpStatus.OK); } throw new BadCredentialsException("Invalid User name or password"); } }
JWT Token生成过滤器代码
package com.vinitpk.instagramapi.instagram.configuration; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.crypto.SecretKey; import java.io.IOException; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Set; @Component public class JwtTokenGeneratorFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(authentication != null){ SecretKey key = Keys.hmacShaKeyFor(SecurityContext.JWT_KEY.getBytes()); String jwt = Jwts.builder() .issuer("instagram") .issuedAt(new Date()) .claim("authorities", populateAuthorities(authentication.getAuthorities())) .claim("userName", authentication.getName()) .expiration(new Date(new Date().getTime() + (10 * 60 * 60 * 1000))) .signWith(key).compact(); response.setHeader(SecurityContext.Header, jwt); System.out.println(jwt); } filterChain.doFilter(request, response); } public String populateAuthorities(Collection<? extends GrantedAuthority> collection){ Set<String> authorities = new HashSet<>(); for(GrantedAuthority authority : collection){ authorities.add(authority.getAuthority()); } return String.join(",", authorities); } protected boolean shouldNotFilter(HttpServletRequest httpServletRequest) throws ServletException{ return !httpServletRequest.getServletPath().equals("/signin"); } }
原因分析
这是浏览器CORS响应头暴露限制导致的:浏览器默认仅允许前端访问部分标准响应头(如Content-Type、Cache-Control),对于自定义或非标准响应头(此处的Authorization),必须由后端通过Access-Control-Expose-Headers明确声明允许暴露,否则前端无法通过res.headers.get()获取到该头信息。
解决方案
1. Spring Boot后端配置CORS,暴露Authorization头
可以选择全局配置或局部接口配置:
方式一:全局CORS配置
创建CORS配置类,统一设置跨域规则并暴露Authorization头:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.List; @Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); // 允许的前端域名,生产环境建议指定具体域名,不要用* config.setAllowedOrigins(List.of("http://localhost:3000")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders(List.of("*")); // 关键:声明暴露Authorization响应头给前端 config.setExposedHeaders(List.of("Authorization")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }
方式二:局部接口配置
若仅需给登录接口配置,可在AuthController类上添加@CrossOrigin注解:
@CrossOrigin(origins = "http://localhost:3000", exposedHeaders = "Authorization") @RestController public class AuthController { // 原有代码不变 }
2. 前端验证
重启后端服务后,前端再次调用登录接口,此时res.headers.get("Authorization")即可正确获取到JWT Token。
注意:后续请求携带Token时,需要给Token添加Bearer 前缀(如果后端验证时要求),示例:
const token = res.headers.get("Authorization"); localStorage.setItem("token", `Bearer ${token}`);
内容的提问来源于stack exchange,提问作者Vinit Pk
相关产品推荐
相关产品推荐

