springboot——跨域(nginx,cors)

跨域访问,是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com 页面去请求 www.google.com 的资源。但是一般情况下不能这么做跨域访问,因为有浏览器的“同源策略”存在,这是浏览器对JavaScript施加的安全限制。

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。

“同源”是指三个相同,协议相同、域名相同、端口相同,只有有任何一个地方不同,就认为是跨域。

随着互联网的发展,“同源政策”越来越严格,目前,如果非同源(跨域),共有三种行为受到限制:

Cookie 、LocalStorage、IndexDB 无法访问
DOM 无法获取
AJAX请求不能发送

我们在项目中,需要设置对跨域访问的支持,是因为项目的架构需要,例如

公司内部有多个不同的子系统,例如A和B,分别部署在不同的服务器上,其域名也不相同
由于公司内部的数据需要,现在A系统中,跨域访问B系统,从而获取内部的一些信息资源

在前后端分离的项目中,前端页面部署在一个服务器上,后端项目部署在另一个服务器上,从前端页面上发送ajax请求到后端系统中,这种情况,就属于跨域访问

案例

新建项目,springboot-html
springboot——跨域(nginx,cors)_第1张图片
页面代码:ajax.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <!--<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>-->
    <!--不需要写static-->
    <script src="js/jquery.js"></script>
    <script>
        $(function(){

            $(".btn1").on("click",function(){
                $.ajax({
                    type: "GET",
                    url : "http://localhost:8989/hi",
                    success: function(msg){
                        console.log(msg);
                    },
                    error: function(){
                        alert('error');
                    }
                });
            });

        });
    </script>

</head>
<body>
<div>
    <button class="btn1">test1</button>

</div>
</body>
</html>

pom文件

<dependencies>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
		
		<dependency>
		   <groupId>org.springframework.bootgroupId>
		   <artifactId>spring-boot-starter-thymeleafartifactId>
		dependency>
		
	dependencies>

配置文件application.properties

server.port=9999

Controller

package com.briup.cms.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {
	
	@GetMapping("/ajax")
	public String hello() {
		return "ajax";
	}
	
}


运行访问
springboot——跨域(nginx,cors)_第2张图片
再构建提供接口访问的后端项目

新建项目,springboot-cors
springboot——跨域(nginx,cors)_第3张图片
pom文件

<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>

配置文件,application.properties

server.port=8989

Controller

package com.briup.cms.web.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@GetMapping("/hi")
	public String hello() {
		return "hello world";
	}
	
}

启动,访问
springboot——跨域(nginx,cors)_第4张图片
访问springboot-html项目中的ajax.html页面,点击页面中的按钮,发送ajax请求,访问springbootcors
项目中的接口:

当前页面的访问地址: http://localhost:9999/ajax
点击按钮后请求地址: http://localhost:8989/hi
可以看出,此时这俩个地址,属于“非同源”,本次访问属于跨域访问。

点击后,控制台上输出的错误信息:
已拦截跨源请求:同源策略禁止读取位于 http://localhost:8989/hi 的远程资源。
(原因:CORS 头缺少 ‘Access-Control-Allow-Origin’)。

点开网络模块,查看具体的请求信息:
请求头中,自动添加了信息 Origin: http://localhost:9999 ,通知服务器本次是跨域访问
springboot——跨域(nginx,cors)_第5张图片
从本次响应的内容中,可以看到,其实响应的内容已经成功返回了,但是由于浏览器的同源政策,把这些结果都给舍弃了。并且执行了Ajax中的回调函数error

解决

避开同源政策给跨域访问带来的限制,方式有很多:

nginx
CORS
JSONP
WebSocket
Node.js
document.domain+iframe
other

nginx

Nginx是一款开源的、高性能的HTTP和反向代理服务器

也是一个IMAP/POP3/SMTP代理服务器
由C语言编写,其系统开销、CPU使用效率都很优秀
性能稳定、功能丰富、配置简洁
服务器中没有web容器

正向代理,一般需要自己手动进行配置:(已知代理服务器)
代理服务器和客户端在同一个网络中。

反向代理,一般是指以代理服务器来接收网络上的请求,然后将请求再转发给内部网络上的其他服务器:
代理服务器和Web服务器在同一个网络中。一般不需要用户自己设置服务器,用户甚至感受不到自己访问的是反向代理服务器。

例如,Nginx的反向代理功能,解决访问跨域问题,或者实现多个服务器负载均衡的效果

步骤

特别注意,nginx启动的时候,一定不要存放在有中文的路径中,如果启动失败,查看错误的日志记录

修改nginx的配置文件nginx.conf
springboot——跨域(nginx,cors)_第6张图片
conf.config

location /html {
    rewrite ^/html/(.*)$ /$1 break;
    proxy_pass http://localhost:9999/;
}

当访问指定地址 http://localhost/html 的时候,会先重写URI地址
URI符合正则表达式^/api/(.*)$ 的地址会被重写为 /$1
$1表示正则表达式中第一个()中的字符串
最后把重写后的新地址,转发给http://localhost:9999/
location /api {
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass http://localhost:8989/;
}

ajax页面中,新增按钮和ajax请求

  $(".btn2").on("click",function(){
                $.ajax({
                    type: "GET",
                    url : "/api/hi",
                    success: function(msg){
                        console.log(msg);
                    },
                    error: function(){
                        alert('error');
                    }
                });
            });

<button class="btn2">test2button>

启动nginx服务器,访问地址
在这里插入图片描述
使用nginx之后,访问springboot-html中页面的地址: http://localhost/html/ajax

但其实是使用nginx代理服务器来“欺骗”浏览器,让浏览器认为是 “同源访问”。浏览器中,从始至终都是访问http://localhost/ 下面的资源,所以浏览器认为一直是“同源访问”。浏览器不知道的是,它访问的服务器是一个nginx代理服务器,它会接收浏览器请求,重写URI地址后,转发给真正的服务器。

cors

1、概念
CORS(Cross-origin resource sharing),是一个W3C标准,全称是”跨域资源共享”。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。
对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息。(Origin)
有时还会多出一次附加的请求,但用户不会有感觉。(options方式的请求)

2、分类
浏览器将CORS请求分成两类:

简单请求(simple request)
非简单请求(not-so-simple request)

只要同时满足以下两大条件,就属于简单请求:

请求方法是以下三种方法之一
GET
POST
HEAD
HTTP的头信息不超出以下几种字段
Accept
Accept-Language

​ Content-Language
​ Last-Event-ID
​ Content-Type,该字段的值只能是以下三种

​ application/x-www-form-urlencoded

​ multipart/form-data
​ text/plain

注意,只要不能同时满足上面两个条件,就属于非简单请求。

简单请求

对于简单请求,浏览器直接发出CORS请求,同时在请求头中增加一个Origin字段。
该字段表示,本次请求来自哪个源(协议 + 域名 + 端口),服务器根据这个值,决定是否同意这次请
求。

Origin指定的源,即使不在服务器许可范围内,服务器还是会返回一个正常的HTTP响应,但是响应头中
不含指定Access-Control-Allow-Origin字段,浏览器这时候就知道本次跨域访问失败。
但是这个时候,响应状态可能是200,同时在一些工具中还能看到正确的返回值。
例如,
springboot——跨域(nginx,cors)_第7张图片
其中,

浏览器在请求头中自动添加了Origin 字段
服务器在响应中没有添加Access-Control-Allow-Origin 字段(说明服务器不支持此请求跨域访
问)
响应的状态码是200,并且从响应内容中可以看到正确的返回内容
同时,浏览器会抛出一个错误,被ajax的核心对象XMLHttpRequest的onerror 回调函数捕获

如果Origin指定的域名在服务器的许可范围内,服务器返回的响应,会多出几个头信息字段:

Access-Control-Allow-Origin
该字段是必须的。
它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的跨域请求。

Access-Control-Allow-Credentials

该字段可选
表示是否允许发送Cookie。
设为true,即表示服务器已经允许了,跨域请求中可以携带Cookie。
但是这需要AJAX中设置withCredentials=true进行配合。
如果服务器不要浏览器发送Cookie,删除该字段即可。

Access-Control-Expose-Headers
该字段可选
​ 列出了哪些首部可以作为响应的一部分暴露给外部。
​ 默认情况下,只有七种可以暴露给外部
​ Cache-Control
​ Content-Language
​ Content-Length
​ Content-Type
​ Expires
​ Last-Modified
​ Pragma
如果想要让客户端可以访问到其他的首部信息,可以将它们在Access-Control-Expose-Headers里面指定

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段
的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词
和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
例如:

$(".btn1").on("click",function(){
$.ajax({
        type: "GET",
        url : "http://localhost:8989/hi",
        contentType: "application/json",
        success: function(msg){
        console.log(msg);
        },
        error: function(){
        alert('error');
        }
    });
});

springboot——跨域(nginx,cors)_第8张图片
“预检”成功的常见响应头部字段有:

Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Allow-Credentials (标签允许客户端携带验证信息,例如 cookie)

ajax跨域访问的时候,如果需要在请求中携带cookie,需要有以下设置:
1.前端ajax请求中,指定withCredentials属性为ture

$.ajax({
    type: "GET",
    url : "http://localhost:8989/hi",
    xhrFields:{
   		 withCredentials: true
    },
    success: function(msg){
    console.log(msg);
    },
    error: function(){
    alert('error');
    }
});

2.后端响应头中,设置Access-Control-Allow-Credentials属性为true

//允许跨域携带cookie
response.setHeader("Access-Control-Allow-Credentials", "true");

3.后端响应头中,设置Access-Control-Allow-Origin属性为具体的值,而不是能通配符*

//response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:9999");

@RestController
public class HelloController {
	// 允许cookie的情况下 前端配置 后台配置
	@GetMapping("/hi")
	public String hello(HttpServletResponse response) {
		response.setHeader("Access-Control-Allow-Credentials", "true");
		response.setHeader("Access-Control-Allow-Origin", "http://localhost:9999");
		return "hello world";
	}
   // 不允许cookie的情况下
   @GetMapping("/hi")
	public String hello(HttpServletResponse response) {
		response.setHeader("Access-Control-Allow-Origin", "*");
		return "hello world";
	}
	
}

可添加拦截器(了解)

package com.briup.cms.web.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;

public class CosInterceptor implements HandlerInterceptor{

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		response.setHeader("Access-Control-Allow-Credentials", "true");
		response.setHeader("Access-Control-Allow-Origin", "http://localhost:9999");
		response.setHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, X-Custom-Header, Authorization");
		System.out.println("跨域");
		return true;
	}	
}

package com.briup.cms.config;

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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.briup.cms.web.interceptor.CosInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(cosInterceptor())
                .addPathPatterns("/**");
    }
    @Bean
    public CosInterceptor cosInterceptor() {
        return new CosInterceptor();
    }	
}


注意,这里后端代码使用的使用response设置的,在springmvc中,也可以专门的设置方法来代替这种方式

使用

springboot-html项目中的ajax.html页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <!--<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>-->
    <script src="js/jquery.js"></script>
    <script>
        $(function(){

            $(".btn1").on("click",function(){
                $.ajax({
                    type: "GET",
                    url : "http://localhost:8989/hi",
                    success: function(msg){
                        console.log(msg);
                    },
                    error: function(){
                        alert('error');
                    }
                });
            });

            $(".btn2").on("click",function(){
                $.ajax({
                    type: "GET",
                    url : "/api/hi",
                    success: function(msg){
                        console.log(msg);
                    },
                    error: function(){
                        alert('error');
                    }
                });
            });
            
            $(".btn3").on("click",function(){
                $.ajax({
                    type: "GET",
                    url : "http://localhost:8989/hi",
                    success: function(msg){
                        console.log(msg);
                    },
                    error: function(msg){
                        alert('error');
                    }
                });
            });

        });
    </script>

</head>
<body>
<div>
    <button class="btn1">test1</button>
    <button class="btn2">test2</button>
    <button class="btn3">test3</button>
</div>
</body>
</html>

springboot-cors项目中的Controller:

package com.briup.cms.web.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@GetMapping("/hi")
	public String hello() {
		return "hello world";
	}
	
}

springboot-cors项目中的配置类:

package com.briup.cms.config;

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("*")//运行所有客户端访问
        .allowCredentials(false)//不允许携带cookie
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")//
        支持的方法
        .allowedHeaders("*")//运行所有请求头字段
        .maxAge(3600);//允许客户端缓存“预检请求”中获取的信息,3600秒
    }
	
}


启动俩个项目,访问页面,点击按钮,测试ajax跨域访问:

可以看出,此时ajax请求进行跨域访问,已经成功

注意,此时可以尝试在ajax请求中,设置contentType: application/json ,观察是否会发出“预检”请求

同时,也可以修改ajax请求,让其写的cookie和指定字段的头部:

$(".btn3").on("click",function(){
    $.ajax({
        type: "GET",
        url : "http://localhost:8989/hi",
        xhrFields:{
        withCredentials: true
        },
        headers:{
        token:"aaa.bbb.ccc"
        },
        contentType: "application/json",
        success: function(msg){
        console.log(msg);
        },
        error: function(msg){
        alert('error');
        }
    });
});

此时,对应的后端跨域设置为:

@Configuration
public class WebConfig implements WebMvcConfigurer{
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        .allowedOrigins("http://localhost:9999")
        .allowCredentials(true)
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
        .allowedHeaders("*")
        .maxAge(3600);
    }
}

其他

@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")//映射所有路径
    .allowedOrigins("*")//运行所有客户端访问
    .allowCredentials(false)//不允许携带cookie
    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")//支持的方法
    .allowedHeaders("*")//运行所有请求头字段
    .maxAge(3600);//允许客户端缓存“预检请求”中获取的信息,3600秒
}

在此配置中,如果设置allowCredentials(true) ,那么allowedOrigins(“*”) 这里就不能使用通
配符了,必须要写一个或者多个(可变参数)客户端的地址
例如, allowedOrigins(“http://127.0.0.1:9999")

另外,如果只是想让Controller中的某一个方法或者几个方法被跨域访问,那么可以在方法上使用
@CrossOrigin 注解,例如

@RestController
public class HelloController {
    @CrossOrigin(origins = {"http://127.0.0.1:9999"})
    @GetMapping("/hi")
    public String hello() {
    return "hello world";
    }
}

此时就不需要在配置类中做其他配置了,直接一个注解就可以让该方法被跨域访问了

你可能感兴趣的:(springboot,spring,boot)