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

Spring Boot无法在浏览器中设置JWT Cookie问题求助

JWT Token无法存入浏览器Cookie的解决方案

已成功生成JWT Token,登录功能逻辑正常,但JWT Token无法存入浏览器Cookie。Postman测试完全正常——每次向signin路由发送POST请求,返回的Cookie头包含JSESSIONID和authToken,且authToken与最新生成的JWT Token匹配,但浏览器端无法看到该Cookie。


相关代码

Spring Boot服务代码

@Override
public String login(LoginDTO loginDto, HttpServletRequest request, HttpServletResponse response) {

    Optional<UserEntity> userOptional = userRepository.findByEmail(loginDto.getEmail());

    if (userOptional.isEmpty()) {
        throw new InvalidRequestException(messageSource.getMessage("EMAIL_OR_PASSWORD_INVALID", null, myLocaleResolver.resolveLocale(request)));
    }

    try {
        UserEntity user = userOptional.get();

        if (!user.isEnabled()) {
            throw new InvalidRequestException(messageSource.getMessage("EMAIL_NOT_VERIFIED", null, myLocaleResolver.resolveLocale(request)));
        }
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginDto.getEmail(),
                        loginDto.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String token = jwtGenerator.generateToken(authentication.getName(), "login");

        Cookie authTokenCookie = new Cookie("authToken", token);
        authTokenCookie.setHttpOnly(true);
        authTokenCookie.setPath("/");
        authTokenCookie.setMaxAge(60 * 60 * 24 * 30); // 30 days

        System.out.println("authTokenCookie Name: " + authTokenCookie.getName());
        System.out.println("authTokenCookie Value: " + authTokenCookie.getValue());

        response.addCookie(authTokenCookie);

        return token;

    } catch (BadCredentialsException e) {
        throw new InvalidRequestException(messageSource.getMessage("EMAIL_OR_PASSWORD_INVALID", null, myLocaleResolver.resolveLocale(request)));
    }
}

Angular代码

registerUser(form: NgForm) {
    if (this.email.valid && this.password.valid) {
        const data = {
            email: this.email.value,
            password: this.password.value
        };

        this.isLoading = true;
        this.http.post('http://localhost:8080/signin', data, {
            headers: {'Accept-Language': this.language},
            responseType: 'json'
        }).subscribe(
            (response: any) => {
                setTimeout(() => {
                    this.toastr.success(response.message);
                    this.router.navigate(['/']);
                    this.isLoading = false;
                }, 2500);
            },
            (error: any) => {
                setTimeout(() => {
                    this.toastr.error(error.error);
                    this.isLoading = false;
                }, 1000);
            }
        )
    }
}

服务器日志

authTokenCookie Name: authToken
authTokenCookie Value: <censored>

解决步骤

1. 配置Spring Boot跨域允许凭证

浏览器跨域请求时,服务端必须明确允许携带凭证,否则浏览器会拒绝存储Cookie。添加CORS配置类:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:4200") // 替换为你的Angular项目地址
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true); // 关键:开启凭证允许
    }
}

或直接在登录接口的控制器方法上添加注解:

@CrossOrigin(origins = "http://localhost:4200", allowCredentials = true)

2. 修改Angular请求,开启withCredentials

Angular的HTTP请求默认不携带凭证,需显式开启,让浏览器接收并存储跨域Cookie:

registerUser(form: NgForm) {
    if (this.email.valid && this.password.valid) {
        const data = {
            email: this.email.value,
            password: this.password.value
        };

        this.isLoading = true;
        this.http.post('http://localhost:8080/signin', data, {
            headers: {'Accept-Language': this.language},
            responseType: 'json',
            withCredentials: true // 关键:开启凭证携带
        }).subscribe(
            (response: any) => {
                setTimeout(() => {
                    this.toastr.success(response.message);
                    this.router.navigate(['/']);
                    this.isLoading = false;
                }, 2500);
            },
            (error: any) => {
                setTimeout(() => {
                    this.toastr.error(error.error);
                    this.isLoading = false;
                }, 1000);
            }
        )
    }
}

3. 调整Cookie的SameSite属性

现代浏览器默认SameSite策略会阻止跨域Cookie存储,需根据环境设置合适的属性:

Cookie authTokenCookie = new Cookie("authToken", token);
authTokenCookie.setHttpOnly(true);
authTokenCookie.setPath("/");
authTokenCookie.setMaxAge(60 * 60 * 24 * 30); // 30天

// 根据请求是否为HTTPS设置SameSite和Secure属性
if (request.isSecure()) {
    authTokenCookie.setSecure(true);
    authTokenCookie.setAttribute("SameSite", "None");
} else {
    // 本地HTTP开发环境设置为Lax,避免浏览器拦截
    authTokenCookie.setAttribute("SameSite", "Lax");
}

response.addCookie(authTokenCookie);

原因说明

Postman不受浏览器同源策略限制,因此能正常接收Cookie;但浏览器会严格执行跨域安全规则,必须同时满足以下条件才能存储跨域Cookie:

  • 服务端CORS配置允许携带凭证
  • 前端请求开启withCredentials
  • Cookie的SameSite属性设置符合跨域要求

内容的提问来源于stack exchange,提问作者Zut4too

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 06:47:18