SpringBoot处理跨域的四种方式
一、介绍
1.1 为什么会出现跨域?
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
1.2 什么是跨域?
当一个请求 url 的协议、域名、端口三者之间任意一个与当前页面 url 不同即为跨域
| 请求页面url | 当前页面url | 是否跨域 | 原因 |
| http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
| http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
| http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
| http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
| http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
1.3 非同源限制
【1】无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
【2】无法接触非同源网页的 DOM
【3】无法向非同源地址发送 AJAX 请求
二、案例
假设我们是前后段分离的项目,分别部署在以下两个ip上
前端页面的地址为 http://127.0.0.1:8848/test/index.html
后台服务的地址为 http://99.48.59.195:8082/
前后端的主要代码如下所示:
后端接口 HelloController.java
import com.example.security.entity.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController public class HelloController { @GetMapping("/testGet") public String testGet(String username) { return username; } @GetMapping("/testGet2") public String testGet2(String username, String password) { return username + "," + password; } @PostMapping("/testPost") public Map testPost(@RequestBody Map<String, Object> map) { return map; } @PostMapping("/testPost2") public User testPost2(User user) { return user; } }
前端页面 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <link type="test/css" href="css/style.css" rel="stylesheet"> <body> <input type="text" style="width: 220px;" id="urlText" value="http://99.48.59.195:8082/testGet" /> <input type="button" id="cors" value="testGet" /><br /> <input type="text" style="width: 220px;" id="urlText1" value="http://99.48.59.195:8082/testGet2" /> <input type="button" id="cors1" value="testGet2" /><br /> <input type="text" style="width: 220px;" id="urlText2" value="http://99.48.59.195:8082/testPost" /> <input type="button" id="cors2" value="testPost" /><br /> <input type="text" style="width: 220px;" id="urlText3" value="http://99.48.59.195:8082/testPost2" /> <input type="button" id="cors3" value="testPost2" /> <script type="text/javascript" src="jquery-3.4.1.min.js"></script> <script type="text/javascript"> $(function() { $("#cors").click( function() { var url2 = $("#urlText").val(); $.get({ url: url2, data: "username=jack", success: function(data) { alert("username is " + data); } }) }); $("#cors1").click( function() { var url2 = $("#urlText1").val(); $.get(url2, { username: "John", password: "2pm" }, function(data) { alert("Data Loaded: " + data); }); }); $("#cors2").click( function() { var url2 = $("#urlText2").val(); $.post({ dataType: 'application/json', contentType: 'application/json', url: url2, data: JSON.stringify({ username: "John", password: "2pm" }), // 指定dataType为json时可能不能执行success回调,可参考https://blog.csdn.net/zls986992484/article/details/51404429 success: function(data) { console.log(11); alert("success"); } }) }); // 这种方式参数为formDate格式 $('#cors3').click(function() { var url2 = $("#urlText3").val(); $.post( url2, { username: 'admin', password: '123' }, function(result) { alert("success"); }, "json" ); }); }); </script> </body> </html>

直接调用接口时,根据浏览器的同源策略可以知道如果我们此时不进行跨域处理的话,访问后端地址是会失败的,控制台会打印如下错误信息

三、解决方案
3.1 实现WebMvcConfigurer,重写跨域处理方法
添加 CORS 的配置信息,我们创建一个 CORSConfiguration 配置类重写如下方法,如下所示:
WebMvcConfigurer.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 这里我们的CORSConfiguration配置类继承了WebMvcConfigurer父类并且重写了addCorsMappings方法,我们来简单介绍下我们的配置信息
* allowedOrigins:允许设置的请求域名访问我们的跨域资源,可以固定单条或者多条内容,如:"http://www.baidu.com",只有百度可以访问我们的跨域资源。
* addMapping:配置可以被跨域的路径,可以任意配置,可以具体到直接请求路径。
* allowedMethods:设置允许的请求方法类型访问该跨域资源服务器,如:POST、GET、PUT、OPTIONS、DELETE等。
* allowedHeaders:允许所有的请求header访问,可以自定义设置任意请求头信息,如:"X-YYYY-TOKEN"
* allowCredentials: 是否允许请求带有验证信息,用户是否可以发送、处理 cookie
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")//项目中的所有接口都支持跨域
.allowedOrigins("*")//所有地址都可以访问,也可以配置具体地址
.allowCredentials(true) //是否允许请求带有验证信息
.allowedMethods("*")//"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
.allowedHeaders("*").maxAge(3600);// 跨域允许时间
}
}
3.2 使用过滤器
方案一:
配置如下过滤器
CorsFilter.java
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 这里填写你允许进行跨域的主机ip,*表示所有(正式上线时可以动态配置具体允许的域名和IP)
// response.setHeader("Access-Control-Allow-Origin", "*");
HttpServletRequest request = (HttpServletRequest) servletRequest;
//获取来源网站
String originStr = request.getHeader("Origin");
//允许该网站进行跨域请求
response.setHeader("Access-Control-Allow-Origin", originStr);
// 允许的访问方法
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
// Access-Control-Max-Age 用于 CORS 相关配置的缓存
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, client_id, uuid, Authorization");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
//表示是否允许请求携带凭证信息,若要返回cookie、携带seesion等信息则将此项设置为true
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Pragma", "no-cache");
filterChain.doFilter(servletRequest, response);
}
@Override
public void destroy() {
}
}
方案二:
利用过滤器配置跨域还可以使用如下方法
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsFilter {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
//表示允许所有,可以设置需要的地址
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
//表示是否允许请求带有验证信息
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//CORS配置对所有接口都有效
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
3.3 使用 @CrossOrigin 注解
import com.example.security.entity.User;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 代码说明:
* @CrossOrigin这个注解可以用在方法上,也可以用在类上,用在类上时,表示该controller所有映射都支持跨域请求。
* 如果不设置他的value属性,或者是origins属性,就默认是可以允许所有的URL/域访问。
* value属性可以设置多个URL。
* origins属性也可以设置多个URL。
* maxAge属性指定了准备响应前的缓存持续的最大时间。就是探测请求的有效期。
* allowCredentials属性表示用户是否可以发送、处理 cookie。默认为false
* allowedHeaders 属性表示允许的请求头部有哪些。
* methods 属性表示允许请求的方法,默认get,post,head。
*/
//直接在Controller类上面添加/@CrossOrigin注解。表示该controller所有映射都支持跨域请求。
//@CrossOrigin(origins = "http://127.0.0.1:8848", maxAge = 3600)
@CrossOrigin
@RestController
public class HelloController {
@GetMapping("/testGet")
public String testGet(String username) {
return username;
}
@GetMapping("/testGet2")
public String testGet2(String username, String password) {
return username + "," + password;
}
@PostMapping("/testPost")
public Map testPost(@RequestBody Map<String, Object> map) {
return map;
}
@PostMapping("/testPost2")
public User testPost2(User user) {
return user;
}
}
3.4 nginx 转发请求处理跨域
前面我们介绍过跨域产生的几种情况,只要保证同源(协议、域名、端口号相同),就不会出现跨域问题。
我们现在前端页面服务器所在IP为 http://127.0.0.1:8848
需要调用的后台服务的地址为 http://99.48.59.195:8082/test/**
那么我们可以在前端服务器的 nginx 配置文件中添加如下代理:
server {
listen 8084;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /usr/local/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /test/ {
proxy_pass http://99.48.59.195:8082/test/;
proxy_read_timeout 150;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
这段配置表示的当前端服务器调用 8084 端口的请求时,会自动将请求转发到 http://99.47.134.33:8090/ 。对于前端请求来说此时的协议、域名、端口号都是相同的,那么就不会出现跨域问题。
三、测试
点击按钮调用接口,成功返回数据,说明我们这里成功进行了跨域处理。

注意:
1.如果项目带有登录功能,需要验证登录凭证cookie时,此时需要在跨域配置中设置 Access-Control-Allow-Credentials 属性:
//表示是否允许请求携带凭证,若要返回cookie、携带seesion等信息则将此项设置为true
response.setHeader("Access-Control-Allow-Credentials", "true");
否则会出现如下错误信息,这句话明确表明了此时要将 Access-Control-Allow-Credentials 头设置为 true
The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'

2.在使用过滤器方案一处理跨域时,如果使用了如下配置:
// 这里填写你允许进行跨域的主机ip,*表示所有(正式上线时可以动态配置具体允许的域名和IP)
response.setHeader("Access-Control-Allow-Origin", "*");
//表示是否允许请求携带凭证信息,若要返回cookie、携带seesion等信息则将此项设置为true
response.setHeader("Access-Control-Allow-Credentials", "true");
这里表示请求需要携带凭证信息,允许所有 ip 进行跨域。理论上是没有问题的,但是在测试的时候会发现控制台会抛出如下错误信息:

错误表明当请求的凭据模式为 “include” 时,响应中的标头不可以使用通配符 “*”。需要指定域名,这时我们可以对跨域配置作如下修改:
HttpServletRequest request = (HttpServletRequest) servletRequest;
//获取来源网站
String originStr = request.getHeader("Origin");
//允许该网站进行跨域请求
response.setHeader("Access-Control-Allow-Origin", originStr);
//表示是否允许请求携带凭证信息,若要返回cookie、携带seesion等信息则将此项设置为true
response.setHeader("Access-Control-Allow-Credentials", "true");
参考:什么是跨域?跨域解决方法
本博客文章均已测试验证,欢迎评论、交流、点赞。
部分文章来源于网络,如有侵权请联系删除。
转载请注明原文链接:https://www.cnblogs.com/sueyyyy/p/10129575.html

浙公网安备 33010602011771号