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

Spring Boot中HttpOnly Cookie验证失效问题及校验方法咨询

问题描述

我在Spring Boot项目里把认证令牌存储在HttpOnly Cookie中,用户登录时会设置携带令牌的Http Cookie,相关代码如下:

登录处理代码

@PostMapping("/processlogin")
@ResponseBody
public ResponseEntity<String> processLogin(
    @RequestBody SigninDataDto signinDataDto,
    HttpServletResponse response
) {
    if(signinDataDto.getUserName()!=null && signinDataDto.getPassword()!=null) {
        String userName = signinDataDto.getUserName();
        String password = Common.sha256Hash(signinDataDto.getPassword());
        boolean rememberMe = signinDataDto.isRememberMe();
        UserDetails customUserDetails = new UserService(userRepository).loadUserByUsername(userName);
        if(password.equals(customUserDetails.getPassword())) {
            //Authenticate by cookie
            Cookie jwtCookie = new Cookie(Constants.JWT_NAME, jwtService.generateToken((CustomUserDetails) customUserDetails));
            if(rememberMe) {
                jwtCookie.setMaxAge(7*24*60*60);
            }
            jwtCookie.setPath("/");
            jwtCookie.setHttpOnly(true);
            response.addCookie(jwtCookie);
            return ResponseEntity.ok("success");
        }
    }
    log.error("Login failed!");
    return ResponseEntity.badRequest().body("failed");
}

JWT认证过滤器代码

package com.springdemo.library.security;

import com.springdemo.library.services.JwtService;
import com.springdemo.library.services.UserService;
import com.springdemo.library.utils.Common;
import com.springdemo.library.utils.Constants;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtService jwtService;
    @Autowired
    private UserService customUserDetailsService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwt(request);

            if (StringUtils.hasText(jwt) && jwtService.validateToken(jwt)) {
                String userName = jwtService.getUserNameFromJWT(jwt);
                UserDetails userDetails = customUserDetailsService.loadUserByUsername(userName);
                if(userDetails != null) {
                    UsernamePasswordAuthenticationToken
                            authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                            userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);
                } else {
                    response.sendRedirect("/login");
                }
            }
        } catch (Exception ex) {
            log.error("failed on set user authentication", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwt(HttpServletRequest request) {
        Cookie cookie = Common.getCookie(request, Constants.JWT_NAME);
        if(cookie!=null) {
            String token = cookie.getValue();
            if(StringUtils.hasText(token)) {
                return token;
            }
        }
        return null;
    }
}

Security配置代码

@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(AbstractHttpConfigurer::disable)
            .authorizeRequests(authorizeRequests ->
                    authorizeRequests
                            .requestMatchers("/login", "/processlogin").permitAll()
                            .anyRequest().authenticated()
            );
    http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

前端登录JS代码

$('document').ready(function () {
    $('#id-form-login').on('submit', function (e) {
        e.preventDefault();
        $.ajax({
            url: '/Library/processlogin',
            method: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                userName: $("#login-username").val(),
                password: $("#login-password").val(),
                rememberMe: $("#rememberme").prop("checked")
            }),
            success: function () {
                window.location.replace("/Library/home");
            },
            error: function (jqXHR, textStatus, errorThrown) {
                console.warn('Error:', textStatus, errorThrown);
            }
        });
    });
});

核心问题:
当过滤器中不校验JWT Cookie的HttpOnly属性时功能正常,但添加校验后,浏览器无法访问/Library/home。明明已将Cookie设置为HttpOnly,为何过滤器无法验证该属性?同时求Java/Spring Boot中校验Cookie是否为HttpOnly的方法。


问题分析与解决方案

原因解析

HttpOnly是Cookie的响应头约束属性,作用是禁止前端JavaScript读取Cookie,但浏览器向服务器发送请求时,并不会把HttpOnly这个标记传递给服务器。也就是说,服务器接收到的Cookie对象里,不会包含HttpOnly的状态信息——这个属性只在浏览器端生效,是浏览器的安全控制逻辑,并非服务器与浏览器之间的传输属性。

所以你在过滤器里调用cookie.isHttpOnly()时,返回的永远是false,导致校验失败,请求被拦截,无法访问目标页面。

正确处理方式

服务器端不需要校验Cookie的HttpOnly属性,因为这个属性是你在设置Cookie时主动控制的。只要在登录接口中严格调用jwtCookie.setHttpOnly(true),就能确保Cookie是HttpOnly的,无需在接收请求时做额外校验。

如果一定要强行在服务器端判断(无实际业务价值),只能通过解析原始的Cookie请求头字符串来实现,但步骤繁琐且没必要,示例代码如下(仅作演示):

private boolean isCookieHttpOnly(HttpServletRequest request, String cookieName) {
    String cookieHeader = request.getHeader("Cookie");
    if (cookieHeader == null) return false;
    String[] cookies = cookieHeader.split(";");
    for (String cookieStr : cookies) {
        cookieStr = cookieStr.trim();
        if (cookieStr.startsWith(cookieName + "=")) {
            // 检查后续是否包含HttpOnly标记
            return cookieHeader.contains("HttpOnly");
        }
    }
    return false;
}

修复后的过滤器调整

如果之前添加了HttpOnly校验逻辑,直接删除即可,恢复原本的令牌校验流程:

private String getJwt(HttpServletRequest request) {
    Cookie cookie = Common.getCookie(request, Constants.JWT_NAME);
    if(cookie!=null) {
        String token = cookie.getValue();
        if(StringUtils.hasText(token)) {
            // 移除HttpOnly校验逻辑
            return token;
        }
    }
    return null;
}

额外安全建议

  1. 生产环境建议给Cookie添加Secure属性,确保仅在HTTPS协议下传输:
    jwtCookie.setSecure(true);
    
  2. 设置SameSite属性,防范CSRF攻击:
    jwtCookie.setSameSite("Strict"); // 或根据业务需求设置为"Lax"
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 08:45:00