web前端跨域访问以及解决方案

web前端跨域访问以及解决方案

 

 

1、什么是跨域?

跨域是因为浏览器的同源策略所导致的。所谓同源是指"协议+域名+端口"三者相同,那么以上条件只要有一个不同,都被当作是不同的即便两个不同的域名指向同一个ip地址,也非同源。浏览器引入同源策略主要是为了防止XSS,CSRF攻击。

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

但是有三个标签是允许跨域加载资源:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>
协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示:

CSRF(Cross-site request forgery),跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

在同源策略影响下,域名A向域名B发送Ajax请求,或操作Cookie、LocalStorage、indexDB等数据,或操作dom,js就会受到限制,但请求css,js等静态资源不受限制

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

跨域的解决方案

1 、通过jsonp跨域

1) JSONP原理

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

2) JSONP和AJAX对比

JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

3) JSONP优缺点

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

jquery ajax请求实现

$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",//可以省略
jsonpCallback:"show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
jsonp:"callback",//->把传递函数名的那个形参callback,可省略
success:function (data){
console.log(data);}
});

jsonp缺点:只能使用get请求,不推荐使用

 

2、 CORS 跨域资源共享

       CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

服务器端对于CORS的支持,主要就是通过设置 Access-Control-Allow-Origin 来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。更多有关跨域资源共享 CORS 的知识

浏览器中可以查看对应的响应头,举个例子,如下

 服务端允许CORS,服务端需要针对接口设置的一系列响应头 (Response Headers)

该字段必需。设置允许请求的域名,多个域名以逗号分隔,也可以设置成 * 即允许所有源访问
Access-Control-Allow-Origin:  http://www.YOURDOMAIN.com
该字段必需。设置允许请求的方法,多个方法以逗号分隔
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
该字段可选。设置允许请求自定义的请求头字段,多个字段以逗号分隔
Access-Control-Allow-Headers: Authorization
该字段可选。设置是否允许发送 Cookies
Access-Control-Allow-Credentials: true      
该字段可选,表示每隔多久才发送一次预检请求
response.addHeader("Access-Control-Max-Age", "1800"),表示隔30分钟才发起预检请求。也就是说,发送两次请求,0表示每次都发送两次

浏览器将CORS请求分成两类:简单请求(simple request)  和  非简单请求(not-so-simple request

1.简单请求

目前大多数情况都采用这种方式。简单请求只需要设置Access-Control-Allow-Origin即可。满足以下两个条件,就属于简单请求

  • 请求方法是这三种方法之一:HEAD,GET,POST
  • HTTP头不超出一下几种字段
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值  application/x-www-form-urlencoded、multipart/form-data、text/plain

2.非简单请求

不符合以上条件的请求就肯定是复杂请求了。
复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。

 
java跨域代码实现:
/**
 * 服务端通过CORS解决跨域访问
 */
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {

    @Override
    public void destroy() {
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");
        // 非简单请求会先发送OPTIONS确认服务端是否支持CORS跨域访问
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
        } else {
            chain.doFilter(req, resp);
        }
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

 

springBoot配置

方式一、全局CORS配置:

/**
 * @date 2019/08/26 22:53
 */
@Configuration
public class GlobalCorsConfig {
    
    // 第一种方式:返回新的CorsFilter(全局跨域)
    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //放行哪些原始域
        config.addAllowedOrigin("*");
        //是否发送Cookie信息
        config.setAllowCredentials(true);
        //放行哪些原始域(请求方式)
        config.addAllowedMethod("*");
        //放行哪些原始域(头部信息)
        config.addAllowedHeader("*");
        //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
        config.addExposedHeader("*");

        //2.添加映射路径
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }

}

 

方式二、使用注解(局部跨域)

// 2、在方法上注解
@Controller
public class MethodAnnotationCors {

    @RequestMapping("/hello")
    @ResponseBody
    @CrossOrigin("http://localhost:8080")
    public String index( ){
        return "Hello World";
    }

}

或者在控制器(@Controller)上使用注解 @CrossOrigin :

// 1、在控制器上注解
@Controller
@CrossOrigin(origins = "http://xx-domain.com", maxAge = 3600)
public class ControllerAnnotationCors {

    @RequestMapping("/hello")
    @ResponseBody
    public String index( ){
        return "Hello World";
    }

}

 

3、使用代理跨域

这个说法相信不陌生,我们依然使用前端域名请求,然后有一个中介商---代理把这个请求转发到真正的后端域名上,那也就不存在跨域问题了。
比较普遍的Nginx,简单的配置一下就可以了。
server{
    # 监听9099端口
    listen 9099;
    # 本地的域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://baidu.com
    location ^~ /api {
        proxy_pass http://baidu.com;
    }    
}
然后前端这边的请求地址是http://localhost:9099/api/xxx,然后Nginx监听到地址是localhost:9099/api的请求,就帮我们转发到真正的服务端地址http://baidu.com



跨域时Cookie设置不到浏览器中问题解决
前后端的主域名一定要保持一致: 比如前端是: dev.baidu.com,  后端的域名也一定是   *. baidu.com 才可以 
 
1: axios 设置允许携带cookie
withCredentials: true

 

2:   Access-Control-Allow-Origin:  不要设置为 * , 要设置成前端的域名

Access-Control-Allow-Credentials: 设置为 true
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
    @Override
    public void destroy() {}

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "http://dev.boleme.net:9000");
//        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, resp);
        }
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException { }
}

 



 



 

posted @ 2020-12-02 23:32  邓维-java  阅读(689)  评论(0)    收藏  举报