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;
HelloClass.java
前端页面 index.html
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Title</title>
</head>
<link type="test/css" href="css/style.css" rel="stylesheet">
<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回调,
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>
index.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");
参考:什么是跨域?跨域解决方法
redis cli 集群命令 redis cluster集群
redis Cluster是官方提供的。和Codis不同的是redis Cluster是去中心化的,每个节点负责群众的一部分数据。
节点之间通过特殊的二进制协议相互交互集群信息
redis cluster(多master + 读写分离 + 高可用)将数据划分为16384的slots,每个节点负责一部分槽。槽信息位于节点中不需要额外的分布式存储