为啥新页面的ajax请求中没有携带Cross-Domain Cookies?

Posted

技术标签:

【中文标题】为啥新页面的ajax请求中没有携带Cross-Domain Cookies?【英文标题】:Why are Cross-Domain Cookies not carried in the new page's ajax request?为什么新页面的ajax请求中没有携带Cross-Domain Cookies? 【发布时间】:2021-03-30 00:52:30 【问题描述】:

我的前端演示中有两个页面,login.htmlhome.html。 当我点击login.html中的“登录”按钮时,一个带有用户名和密码的Ajax请求将被发送到后端。如果我登录成功,页面会跳转到home.html

<body>
    <button id="login-btn">login</button>
    <script>
        $('#login-btn').click(function() 
            $.ajax(
                method: 'POST',
                url: 'http://localhost:8080/api/login',
                contentType: 'application/json',
                dataType: 'json',
                data: JSON.stringify( 'username': 'hover', 'password': 'hover' ),

                success: function(data, status, xhr) 
                    alert(data.success);
                    // jump to home
                    window.location.href = 'home.html';
                ,

                error: function(xhr, error_type, exception) 
                    console.log(error_type);
                
            );
        );
    </script>
</body>

home.html 中有一个“请求”按钮。当我点击“请求”按钮时,一个带有“问候”的Ajax请求将被发送到后端。如果我已登录,我将收到回显响应,否则我将收到“尚未登录”消息。

为了告诉后端“我已经登录了!”,我将withCredentials设置为true,使请求携带包含JSESSIONID的cookie。

<body>
    <button id="request-btn">request</button>
    <script>
        $('#request-btn').click(function () 
            $.ajax(
                method: 'POST',
                url: 'http://localhost:8080/api/echo',
                contentType: 'application/json',
                dataType: 'json',
                data: JSON.stringify( 'greeting': 'hello' ),
                crossDomain: true,
                xhrFields: 
                    withCredentials: true
                ,

                success: function (data, status, xhr) 
                    console.log(data);
                ,

                error: function (xhr, error_type, exception) 
                    console.log(error_type);
                
            );

        );
    </script>
</body>

我使用 SpringMVC 构建我的后端,我已经配置了 CORSLoginInterceptor

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.example")
public class AppConfig implements WebMvcConfigurer 

    //CORS
    @Override
    public void addCorsMappings(CorsRegistry registry) 
        registry.addMapping("/**")
                .allowedOrigins("http://127.0.0.1:5500")
                .allowedMethods("GET", "POST", "OPTIONS")
                .allowedHeaders("content-type", "origin")
                .allowCredentials(true).maxAge(1800);
    

    // Register the LoginInterceptor
    @Override
    public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
    

LoginInterceptor 将拦截除Preflight OPTIONS 和登录请求之外的任何请求。

public class LoginInterceptor implements HandlerInterceptor 
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws IOException 
        // Let OPTIONS pass directly
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) 
            return true;
        

        // Check whether the client has already logged in.
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("username") != null) 
            return true;
        
        else 
            response.setContentType("application/json; charset=utf-8");
            // "message": "not yet logged in"
            response.getWriter().write("\"message\": \"not yet logged in\"");
        

        return false;
    


ApiController 中,login 方法打印用户名和密码,创建session 并存储用户名,并返回“成功”消息。echo 方法返回来自home.html 的问候消息。

@RestController
public class ApiController 

    @PostMapping(value = "/login", produces = "application/json; charset=utf-8")
    public String login(@RequestBody Map<String, String> param, HttpServletRequest request) 
        String username = param.get("username");
        String password = param.get("password");
        System.out.println("username: " + username);
        System.out.println("password: " + password);

        // create a session and store the username
        request.getSession().setAttribute("username", username);
        // "success": true
        return "\"success\": true";
    

    @PostMapping(value = "/echo", produces = "application/json; charset=utf-8")
    public String echo(@RequestBody Map<String, String> param) 
        String greeting = param.get("greeting");
        System.out.println(greeting);
        // "greeting": "hello"
        return "\"greeting\": " + "\"" + greeting + "\"";
    

因为在login方法中创建了一个session,所以登录请求的响应头会设置Set-Cookie字段为JSESSIONID,这就是我想在home.html中携带的内容的请求。

登录请求图片:preflight OPTIONSPOST

但实际上,当我后来跳转到home.html并发送问候请求时,JSESSIONID没有被携带。因此,我收到“尚未登录”消息。我想知道为什么会这样? 图片:preflight OPTIONSPOST which does not carry a cookie

【问题讨论】:

【参考方案1】:

如果我在“登录”Ajax 请求中也设置了它,问题就会解决。 就像代码一样:

<body>
    <button id="login-btn">login</button>
    <script>
        $('#login-btn').click(function() 
            $.ajax(
                method: 'POST',
                url: 'http://localhost:8080/api/login',
                contentType: 'application/json',
                dataType: 'json',
                data: JSON.stringify( 'username': 'hover', 'password': 'hover' ),


                xhrFields: 
                    withCredentials: true
                ,


                success: function(data, status, xhr) 
                    alert(data.success);
                    // jump to home
                    window.location.href = 'home.html';
                ,

                error: function(xhr, error_type, exception) 
                    console.log(error_type);
                
            );
        );
    </script>
</body>

从表面上看,问题解决了。 实际上,我只想让“问候”Ajax 请求携带 cookie,但现在我必须在“登录”Ajax 请求中也将 withCredentials 设置为 true。 那么幕后发生了什么?

XMLHttpRequest.withCredentials 属性是一个布尔值,指示是否应使用 cookie、授权标头或 TLS 客户端证书等凭据进行跨站点访问控制请求。设置 withCredentials 对同站点请求没有影响。 此外,此标志还用于指示何时在响应中忽略 cookie。默认值为假。来自不同域的 XMLHttpRequest 无法为其自己的域设置 cookie 值,除非在发出请求之前将 withCredentials 设置为 true。通过将 withCredentials 设置为 true 获得的第三方 cookie 仍将遵循同源策略,因此请求脚本无法通过 document.cookie 或从响应头访问。

此外,此标志还用于指示何时在响应中忽略 cookie。

【讨论】:

以上是关于为啥新页面的ajax请求中没有携带Cross-Domain Cookies?的主要内容,如果未能解决你的问题,请参考以下文章

AJAX异步请求(JavaScriptjquery)以及参数携带

为啥我的ajax请求返回的页面不跳转

JQuery 中ajax请求为啥总不到后台 浏览器可以正常访问后台 求解!!!

解决携带身份信息的跨域请求

Ajax记录

Ajax跨域请求携带cookie问题