博主
258
258
258
258
专辑

第十五节 SpringBoot实现服务端跨域

亮子 2021-05-11 13:55:20 5270 0 0 0

1、跨域问题的现象

在开发基于WEB应用的时候,一般情况下前端负责界面展示,后端负责业务逻辑,尤其是当前分布式、微服务、前后端分离架构的广泛使用,前端调用后端获取数据,难免会出现数据访问跨域现象:

当浏览器console出现下面的提示,说明出现了跨域问题。

Access to XMLHttpRequest at 'http://localhost:8080/add' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

图片alt

从上面的错误信息,能够看出来几个关键词Origin、CORS、policy等等,这些词究竟是什么含义呢?

  • origin:域、源
  • CORS:全称是“跨域资源共享”(Cross-Origin Resource Sharing)
  • blocked:阻止,阻塞
  • Access-Control-Allow-Origin:HTTP协议中与跨域有关的请求头信息。
  • CORS policy:CORS策略

2、什么是域

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为不同的域。

图片alt

3、为什么会出现跨域问题

出于安全考虑,**浏览器**使用同源策略(SameOriginPolicy)进行访问的限制。

所谓同源(或同一个域)就是两个请求具有相同的协议(protocol),主机(host)和端口号(port),这三者均相同则表示这两个请求同源。

同源策略防止当前域的javascript脚本和另外一个域的内容进行交互,这就是为什么出现跨域访问被阻止的原因。

同源策略是Web应用程序安全模型中的一个重要概念,它由 Netscape 公司在1995年引入浏览器。该策略最初是为保护DOM的访问而设计的,Web浏览器允许第一个Web页面中包含的脚本访问第二个Web页面中的数据,但前提是两个Web页面具有相同的源(origin)。后来进行了扩展,此策略可防止一个页面上的恶意脚本(ajax请求)访问另一个网页上的敏感数据。

图片alt

目前Web应用程序广泛依赖HTTP cookie来维护经过身份验证的用户会话,服务器基于cookie信息来显示或者获取敏感信息。客户端必须保证无关站点与当前站点严格分离,以防止数据机密性或完整性丢失。

同源策略主要防止一个域的javascript脚本和另外一个域的内容进行交互。

主要限制包括:
- 不同源的页面之间Cookie、LocalStorage 和 IndexDB无法共享
- 不同源AJAX 请求不能发送

图片alt

(1)当访问http://www.integer.net.cn的时候,浏览器发送GET 请求获取主页,这时候当前页面的域指定为http://www.integer.net.cn

(2)通过<img src="http://www.integer.net.cn/1.png">访问1.png图片,没有跨域,允许访问,通过<img src="http://www.double.net.cn/2.png">访问2.png图片,虽然跨域,但访问的是HTML嵌入资源,不受同源策略影响,依然可以访问。

(3)通过<link href="http://www.integer.net.cn/a.css" />访问a.css,没有跨域,允许访问,通过< link href="http://www.double.net.cn/b.css">访问b.css,虽然跨域,但访问的是HTML嵌入资源,不受同源策略影响。

(4)使用ajax异步请求http://www.integer.net.cn域下的数据,没有跨域,允许访问

(5)使用ajax异步请求http://www.float.com域下的数据,此时跨域,同源策略阻止继续访问,是否允许访问由CORS策略控制。

从以上分析可以看出,只有当ajax异步请求跨域时,同源策略才会起作用,跨域请求就一定不能访问呢?当然不是,上面已经提及,是否能访问跨域资源由CORS策略决定的,那么什么是CORS策略呢?

4、CORS跨域解决方案(服务端)

CORS策略,它允许浏览器向跨域服务器发出XMLHttpRequest(AJAX)请求,从而克服了AJAX只能同源访问的限制。

整个CORS通信过程,前端都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息(针对简单请求),有时还会多出一次附加的请求(针对复杂请求),但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。

上面这段话,翻译成大白话就是浏览器发现AJAX请求跨域,添加附加信息(简单请求)或者附加请求(复杂请求)发送到服务器,如果服务器端允许当前访问,则继续访问返回响应结果,如果服务器不允许该来源访问,则会出现跨域访问现象。所以,主动权掌握在服务器手里。这就是CORS策略。

就像你想要约隔壁班的女生看电影,人家和你不是一个班根本不认识你,去不去由人家决定。当然,附加的信息就是你表明自己身份去邀请人家,这里还有一个问题,如果只是看电影这种简单请求,你拿着票表达诚意就行。但是对于其他复杂请求,问一次肯定不行,先要进行一次预请求,试探一下,试探成功后再进行复杂请求。

那么哪些请求是简单请求,哪些是复杂请求呢?

简单请求需要满足以下几点:

  • 请求是GET、POST、HEAD三种
  • HTTP的头信息不超出以下几种字段 Accept、Accept-Language、Content-Language、Content-Type
  • Content-Type 的值仅限于下列三者之一:text/plain、multipart/form-data、application/x-www-form-urlencoded

什么是复杂请求呢,除了简单请求都是复杂请求。

比如说你需要发送PUT、DELETE等HTTP请求,或者发送Content-Type: application/json的内容,这些就都是复杂请求。

那么简单请求中附加的信息是什么呢?

跨域请求HTTP头当中要求包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,开发者不需要操作,这就是简单请求附加的信息。

服务器端对简单请求进行响应,部分响应头及解释如下:

  • Access-Control-Allow-Origin(必含) - 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"。
  • Access-Control-Allow-Credentials(可选) - 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest对象(ajax请求)当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则导致请求失败。

图片alt

任何不满足上述简单请求要求的请求,都属于复杂请求。

复杂请求会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求(preflight)。预检请求为OPTIONS请求,用于向服务器请求权限信息的。预检请求被成功响应后,才会发出真实请求,携带真实数据。

图片alt

预请求以OPTIONS形式发送,当中同样包含域,并且还包含了两项CORS特有的内容:

  • Access-Control-Request-Method – 该项内容是实际请求的种类,可以是GET、POST之类的简单请求,也可以是PUT、DELETE等等。
  • Access-Control-Request-Headers – 该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部。
    显而易见,这个预请求实际上就是在为之后的实际请求发送一个权限请求,在预回应返回的内容当中,服务端应当对这两项进行回复,以让浏览器确定请求是否能够成功完成。

复杂请求的部分响应头及解释如下:

  • Access-Control-Allow-Origin(必含) – 和简单请求一样的,必须包含一个域。
  • Access-Control-Allow-Methods(必含) – 这是对预请求当中Access-Control-Request-Method的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。
  • Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。这里在实际使用中有遇到,所有支持的头部一时可能不能完全写出来,而又不想在这一层做过多的判断,没关系,事实上通过request的header可以直接取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Allow-Headers即可。
  • Access-Control-Allow-Credentials(可选) – 和简单请求当中作用相同。
  • Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存。
    一旦预回应如期而至,所请求的权限也都已满足,则实际请求开始发送。

5.Spring CORS –@CrossOrigin注解

Spring MVC 提供@CrossOrigin 注解. 该注解可以标注在类或者方法上

5.1. Spring CORS 配置

@CrossOrigin 默认将允许所有的源,所有头信息,@RequestMapping标注的方法进行跨域访问,maxAge 为30分钟。

图片alt

5.2. @CrossOrigin 注解标注在Controller类

@CrossOrigin(origins = "*", allowedHeaders = "*")
@Controller
public class HomeController 
{
    @GetMapping(path="/")
    public String homeInit(Model model) {
        return "home";
    }
}

表明该类的所有方法,均实现CORS策略。

5.3. @CrossOrigin 注解标注在方法

@Controller
public class HomeController 
{
    @CrossOrigin(origins = "*", allowedHeaders = "*")
    @GetMapping(path="/")
    public String homeInit(Model model) {
        return "home";
    }
}

表明该方法实现CORS策略。

5.4. @CrossOrigin 在方法级覆盖类级注解

@Controller
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class HomeController 
{
    @CrossOrigin(origins = "http://example.com")
    @GetMapping(path="/")
    public String homeInit(Model model) {
        return "home";
    }
}

homeInit() 方法只能被域 http://example.com访问,HomeController 的其他方法可以被所有的域

6.Spring CORS – 全局配置

6.1. 实现WebMvcConfigurer

为了让整个应该所有的请求均实现CORS跨域方案, 可以使用WebMvcConfigurer 并添加到 CorsRegistry中。

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

@Configuration
@EnableWebMvc
public class CorsConfiguration implements WebMvcConfigurer
{
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("GET", "POST");
    }
}

6.2. WebMvcConfigurer Bean

spring boot应用中, 推荐声明一个 WebMvcConfigurer bean.

@Configuration
public class CorsConfiguration 
{
    @Bean
    public WebMvcConfigurer corsConfigurer() 
    {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("http://localhost:8080");
            }
        };
    }
}

6.3 CORS 与Spring Security

为了能够使CORS 支持 Spring security, 配置CorsConfigurationSource bean 并且使用 HttpSecurity.cors() 进行设置。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
            //other config
    }
 
    @Bean
    CorsConfigurationSource corsConfigurationSource() 
    {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

7.Java CORS过滤器

如果应用中没有集成Spring或者SpringBoot框架,可以直接使用过滤器来进行配置,基本原理就是对Http 头信息就行设置。

7.1Response Headers

  • Access-Control-Allow-Origin : 指示允许哪些域进行跨域访问. “*” 代表没有限制.

  • Access-Control-Allow-Credentials : 指示跨域访问是否支持用户身份,即cookie.

  • Access-Control-Expose-Headers : 指示哪些头信息暴露出来.

  • Access-Control-Max-Age : 指示预请求响应在客户端缓存的时间.

  • Access-Control-Allow-Methods : 指示允许哪些方法可以访问资源

  • Access-Control-Allow-Headers :指示在实际请求中哪些头信息被允许.

7.2. Request Headers

  • Origin : 指示跨域请求或者预请求的来源.

  • Access-Control-Request-Method : 在预请求中使用,告诉服务器实际请求讲使用该方法

  • Access-Control-Request-Headers :在预请求中使用,告诉服务器实际请求中将带的头信息.

7.3. Java CORS 过滤器demo

import java.io.IOException;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet Filter implementation class CORSFilter
 */
// Enable it for Servlet 3.x implementations
/* @ WebFilter(asyncSupported = true, urlPatterns = { "/*" }) */
public class CORSFilter implements Filter {

    /**
     * Default constructor.
     */
    public CORSFilter() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest servletRequest, 
                          ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        System.out.println("CORSFilter HTTP Request: " + request.getMethod());

        // Authorize (allow) all domains to consume the content
        ((HttpServletResponse) servletResponse)
          		.addHeader("Access-Control-Allow-Origin", "*");
        ((HttpServletResponse) servletResponse)
          		.addHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST");

        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        // For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshake
        if (request.getMethod().equals("OPTIONS")) {
            resp.setStatus(HttpServletResponse.SC_ACCEPTED);
            return;
        }

        // pass the request along the filter chain
        chain.doFilter(request, servletResponse);
    }

    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        // TODO Auto-generated method stub
    }
 }
web.xml配置

<filter>
    <filter-name>CorsFilter</filter-name>
    <filter-class>com.howtodoinjava.examples.cors.CORSFilter</filter-class>
</filter>
 
<filter-mapping>
    <filter-name>CorsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

8、本文说明

本篇文章抄袭于以下地址:

https://www.toutiao.com/i6869009986618720771/

文章作者**泰哥**是我的好朋友,因为他的这篇文章写的太好了,我承认我是无法超过这篇文章,所以只能是剽窃了。希望大家也关注泰哥的文章,为他点赞。