java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java跨域cookie失效

java跨域cookie失效问题及解决

作者:CC大煊

文章介绍了现代Web应用中前后端分离架构下跨域请求和Cookie处理的问题,包括现象描述、跨域Cookie的原理、解决方案(如Java后端、前端Vue、Nginx配置,以及使用window.localStorage存储数据),以及实践案例和常见问题排查

1. 现象描述

1.1 问题背景

在现代 Web 应用中,前后端分离架构已经成为一种常见的开发模式。前端通常使用 Vue.js 等框架,而后端则使用 Java 等语言构建 API 服务。

在这种架构下,前端和后端可能会部署在不同的域名或端口上,这就引发了跨域请求的问题。跨域请求涉及到浏览器的同源策略,尤其是当涉及到 Cookie 时,问题会变得更加复杂。

1.2 具体现象

当前端应用尝试向后端 API 发送请求并期望后端返回的 Cookie 能够在前端被正常使用时,可能会遇到以下问题:

1.3 常见提示信息

在这种情况下,前端开发者可能会在控制台或网络请求面板中看到以下提示信息:

CORS 错误:浏览器控制台中可能会出现跨域资源共享(CORS)相关的错误信息,例如:

Access to XMLHttpRequest at 'https://api.example.com/resource' from origin 'https://frontend.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这些现象表明,尽管后端服务正常响应,但由于跨域问题,前端未能正确接收到或存储 Cookie,导致后续请求失败。

2. 跨域 Cookie 的原理

2.1 什么是 Cookie

Cookie 是一种由服务器发送并存储在客户端的小型数据文件,用于保存用户的状态信息。它们通常用于以下几种用途:

Cookie 由键值对组成,通常包含以下属性:

2.2 Cookie 的作用域

Cookie 的作用域定义了它们在何种情况下会被发送到服务器。主要包括以下几方面:

2.3 SameSite 属性

SameSite 属性用于防止跨站请求伪造(CSRF)攻击,控制 Cookie 在跨站请求中的发送行为。该属性有三个值:

在实际应用中,如果 SameSite 属性设置不当,可能会导致跨域请求中的 Cookie 失效,从而影响用户的会话管理和状态保持。

3. 解决方案

3.1 Java 后端解决方案

3.1.1 配置 SameSite 属性

为了确保 Cookie 能在跨域请求中被正确发送和接收,可以配置 Cookie 的 SameSite 属性。SameSite 属性有三个值:

示例代码:

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

public void setCookie(HttpServletResponse response) {
    Cookie cookie = new Cookie("key", "value");
    cookie.setPath("/");
    cookie.setHttpOnly(true);
    cookie.setSecure(true);
    cookie.setMaxAge(7 * 24 * 60 * 60); // 1 week
    cookie.setSameSite("None"); // SameSite=None
    response.addCookie(cookie);
}

3.1.2 使用 Spring Boot 设置 Cookie 属性

在 Spring Boot 中,可以通过配置类来设置 Cookie 属性。

示例代码:

import org.springframework.boot.web.server.Cookie.SameSite;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CookieConfig {

    @Bean
    public ServletWebServerFactory servletWebServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addContextCustomizers(context -> {
            context.setSessionCookieConfig(sessionCookieConfig -> {
                sessionCookieConfig.setSameSite(SameSite.NONE.attributeValue());
                sessionCookieConfig.setSecure(true);
            });
        });
        return factory;
    }
}

3.1.3 配置 CORS 解决跨域问题

在 Spring Boot 中,可以通过配置 CORS 来允许跨域请求。

示例代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://your-frontend-domain.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .allowedHeaders("*")
                .maxAge(3600);
    }
}

3.2 前端解决方案

3.2.1 Vue 配置跨域请求

在 Vue 项目中,可以通过配置 vue.config.js 文件来设置代理,以解决开发环境中的跨域问题。

示例代码:

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://your-backend-domain.com',
        changeOrigin: true,
        secure: false,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
};

3.2.2 使用 Axios 发送跨域请求

在 Vue 项目中,通常使用 Axios 来发送 HTTP 请求。可以全局配置 Axios 以支持跨域请求。

示例代码:

import axios from 'axios';

axios.defaults.baseURL = 'http://your-backend-domain.com';
axios.defaults.withCredentials = true; // 允许携带 Cookie

export default axios;

3.2.3 设置 withCredentials 属性

在发送具体请求时,也可以单独设置 withCredentials 属性。

示例代码:

axios.get('/api/some-endpoint', {
  withCredentials: true
}).then(response => {
  console.log(response.data);
});

3.3 Nginx 解决方案

3.3.1 配置 Nginx 处理跨域

在 Nginx 配置文件中,可以通过设置响应头来允许跨域请求。

示例代码:

server {
    listen 80;
    server_name your-backend-domain.com;

    location / {
        add_header 'Access-Control-Allow-Origin' 'http://your-frontend-domain.com';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';

        if ($request_method = 'OPTIONS') {
            return 204;
        }

        proxy_pass http://backend_server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

3.3.2 设置 Cookie 属性

在 Nginx 中,可以通过 proxy_cookie_path 指令来设置 Cookie 的 SameSite 属性。

示例代码:

server {
    listen 80;
    server_name your-backend-domain.com;

    location / {
        proxy_pass http://backend_server;
        proxy_cookie_path / "/; SameSite=None; Secure";
    }
}

3.4 使用 window.localStorage 存储数据

window.localStorage 是一种在浏览器中存储数据的机制,它具有以下优点:

3.4.1 代码示例:存储数据

在需要存储数据的页面中,我们可以使用 window.localStorage.setItem 方法将数据存储到 localStorage 中。假设我们有一个 JSON 对象 jsonData,需要将其中的 redirectData 存储起来。

// 假设 jsonData 是我们需要存储的数据对象
const jsonData = {
    redirectData: "exampleData"
};

// 将数据存储到 localStorage 中
window.localStorage.setItem('redirectData', JSON.stringify(jsonData.redirectData));

// 验证数据是否存储成功
console.log('Data stored in localStorage:', window.localStorage.getItem('redirectData'));

3.4.2 代码示例:获取数据

在目标页面中,我们可以使用 window.localStorage.getItem 方法从 localStorage 中读取数据。

// 从 localStorage 中获取数据
const storedData = window.localStorage.getItem('redirectData');

// 检查数据是否存在
if (storedData) {
    const redirectData = JSON.parse(storedData);
    console.log('Data retrieved from localStorage:', redirectData);
} else {
    console.log('No data found in localStorage.');
}

3.4.3 解决方案的工作原理

使用 window.localStorage 解决跨域 Cookie 失效问题的工作原理如下:

数据存储

数据获取

数据传递

3.4.4 使用场景与限制

使用场景

限制

4. 实践案例

4.1 Java 后端代码示例

在 Java 后端中,我们可以使用 Spring Boot 来设置 Cookie 属性和处理跨域请求。以下是一个简单的示例:

设置 SameSite 属性和跨域配置

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;

@RestController
@RequestMapping("/api")
public class CookieController {

    @PostMapping("/set-cookie")
    @CrossOrigin(origins = "https://frontend.example.com", allowCredentials = "true")
    public String setCookie(HttpServletResponse response) {
        Cookie cookie = new Cookie("key", "value");
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        cookie.setMaxAge(3600); // 1 hour
        cookie.setDomain("example.com");
        cookie.setComment("SameSite=None; Secure"); // For SameSite=None
        response.addCookie(cookie);
        return "Cookie set";
    }
}

配置 CORS

在 Spring Boot 应用中,可以通过配置类来全局配置 CORS:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                        .allowedOrigins("https://frontend.example.com")
                        .allowedMethods("GET", "POST", "PUT", "DELETE")
                        .allowCredentials(true);
            }
        };
    }
}

4.2 Vue 前端代码示例

在 Vue 项目中,我们通常使用 Axios 进行 HTTP 请求。以下是一个示例,展示如何配置 Axios 以支持跨域请求并携带 Cookie:

安装 Axios

npm install axios

配置 Axios

在 Vue 项目的 main.js 文件中配置 Axios:

import Vue from 'vue';
import App from './App.vue';
import axios from 'axios';

axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'https://api.example.com';

Vue.prototype.$axios = axios;

new Vue({
  render: h => h(App),
}).$mount('#app');

发送跨域请求

在 Vue 组件中使用 Axios 发送请求:

<template>
  <div>
    <button @click="setCookie">Set Cookie</button>
  </div>
</template>

<script>
export default {
  methods: {
    setCookie() {
      this.$axios.post('/api/set-cookie')
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
}
</script>

4.3 综合示例:前后端联调

以下是一个综合示例,展示如何在前后端联调中处理跨域 Cookie 问题。

后端代码

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;

@RestController
@RequestMapping("/api")
public class CookieController {

    @PostMapping("/set-cookie")
    @CrossOrigin(origins = "https://frontend.example.com", allowCredentials = "true")
    public String setCookie(HttpServletResponse response) {
        Cookie cookie = new Cookie("key", "value");
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        cookie.setMaxAge(3600); // 1 hour
        cookie.setDomain("example.com");
        cookie.setComment("SameSite=None; Secure"); // For SameSite=None
        response.addCookie(cookie);
        return "Cookie set";
    }
}

前端代码

<template>
  <div>
    <button @click="setCookie">Set Cookie</button>
  </div>
</template>

<script>
export default {
  methods: {
    setCookie() {
      this.$axios.post('/api/set-cookie')
        .then(response => {
          console.log(response.data);
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
}
</script>

<script>
import Vue from 'vue';
import App from './App.vue';
import axios from 'axios';

axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'https://api.example.com';

Vue.prototype.$axios = axios;

new Vue({
  render: h => h(App),
}).$mount('#app');
</script>

通过上述配置,前端发送请求时会携带 Cookie,后端也会正确设置和返回 Cookie,从而实现跨域请求中的 Cookie 管理。

5. 常见问题与排查

5.1 Cookie 未正确设置

问题描述:Cookie 未被浏览器保存或发送。 

排查步骤

5.2 浏览器限制

问题描述:某些浏览器可能对跨域 Cookie 有额外的限制。 

排查步骤

5.3 服务器配置问题

问题描述:服务器配置错误导致跨域请求失败。 

排查步骤

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文