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

Spring Boot与Angular5环境下HttpOnly Cookie存储JWT认证问题

嘿,这个问题我之前做前后端分离项目时也碰到过,把JWT放进HttpOnly + Secure的Cookie里确实是比localStorage安全得多的方案,刚好适配你的Angular 5 + Spring Boot场景,我给你一步步拆解解决方案:

一、先搞定跨域(CORS)配置

因为Angular和Spring Boot是前后端分离架构,跨域是必须解决的前提,而且要确保允许请求携带Cookie,这是Cookie传递的基础。

Spring Boot端全局跨域配置

别用@CrossOrigin的通配符配置,一定要指定具体的Angular域名,否则允许携带Cookie的设置会失效:

@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) // 核心:允许请求携带Cookie
                .maxAge(3600); // 预检请求缓存时间
    }
}

Angular端全局请求配置

通过Http拦截器给所有请求加上withCredentials: true,这样请求会自动携带Cookie:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // 克隆请求并开启携带Cookie的配置
    const authRequest = req.clone({ withCredentials: true });
    return next.handle(authRequest);
  }
}

// 在AppModule里注册拦截器
@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ]
})
export class AppModule { }

用户登录验证通过后,不要把JWT直接返回给前端,而是封装进HttpOnly、Secure的Cookie中:

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
    // 1. 验证邮箱和密码,生成JWT令牌
    Authentication authentication = authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword())
    );
    String jwt = jwtTokenProvider.generateToken(authentication);

    // 2. 创建JWT Cookie
    Cookie jwtCookie = new Cookie("JWT_TOKEN", jwt);
    jwtCookie.setHttpOnly(true); // 关键:禁止JavaScript访问,彻底防范XSS
    jwtCookie.setSecure(true); // 生产环境必须开启:只在HTTPS下传输Cookie
    jwtCookie.setPath("/"); // 让整个应用路径都能访问该Cookie
    jwtCookie.setMaxAge(86400); // 设置有效期,比如24小时

    // 3. 将Cookie放入响应头
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.SET_COOKIE, jwtCookie.toString());

    return ResponseEntity.ok().headers(headers).body("登录成功");
}

注意:开发环境如果用HTTP协议,setSecure(true)会导致Cookie不生效,可以根据环境动态设置:jwtCookie.setSecure(env.acceptsProfiles("prod"))

三、Spring Boot端验证Cookie中的JWT

写一个过滤器,从请求Cookie中提取JWT并完成身份验证:

@Component
public class JwtAuthFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从Cookie中获取JWT
        String jwt = extractJwtFromCookie(request);

        // 验证JWT并设置认证上下文
        if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
            Authentication auth = jwtTokenProvider.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }

    private String extractJwtFromCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("JWT_TOKEN".equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
}

然后把这个过滤器加到Spring Security配置中:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthFilter jwtAuthFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
            .and()
            .csrf().disable() // 临时关闭,后面会讲CSRF防护
            .authorizeRequests()
            .antMatchers("/login").permitAll() // 登录接口放行
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
四、CSRF防护不可少

用Cookie存JWT能防XSS,但CSRF攻击依然存在,必须补上防护:

  1. Spring Boot端:开启CSRF防护(默认是开启的,把上面配置里的csrf().disable()删掉即可),Spring Boot会自动把CSRF令牌存入名为XSRF-TOKEN的HttpOnly Cookie中。
  2. Angular端:HttpClient会自动读取XSRF-TOKEN Cookie,并在请求头中添加X-XSRF-TOKEN,只要你之前配置了withCredentials: true,这个过程完全自动,不用额外写代码。
五、刷新令牌的处理

如果JWT有过期时间,建议额外生成一个刷新令牌(同样存入HttpOnly Cookie),当JWT过期时,前端调用刷新接口获取新的JWT:

@PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken(HttpServletRequest request) {
    String refreshToken = extractRefreshTokenFromCookie(request);
    if (StringUtils.hasText(refreshToken) && jwtTokenProvider.validateRefreshToken(refreshToken)) {
        String newJwt = jwtTokenProvider.generateNewJwt(refreshToken);
        
        // 更新JWT Cookie
        Cookie jwtCookie = new Cookie("JWT_TOKEN", newJwt);
        jwtCookie.setHttpOnly(true);
        jwtCookie.setSecure(true);
        jwtCookie.setPath("/");
        jwtCookie.setMaxAge(86400);

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.SET_COOKIE, jwtCookie.toString());

        return ResponseEntity.ok().headers(headers).body("令牌已刷新");
    }
    return ResponseEntity.badRequest().body("刷新令牌无效");
}

Angular端可以在拦截器中捕获401错误,自动调用刷新令牌接口,然后重试原请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:33:13