关于web项目跨域问题详解

25 篇文章 0 订阅

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

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

总结,发生跨域必须同时满足3个条件:

1.发起请求和接收请求的协议,主机或者端口号不同,比如发起请求所在路径是http://localhost:8080,接收请求所在路径是http://localhost:8081,端口号不同,出现跨域问题

2.通过浏览器发起的请求,比如postman发起的请求就不会出现跨域问题

3.发起的请求类型是xhr(XMLHttpRequest)类型,比如json和document,javascript请求就不会出现跨域问题

为什么在浏览器地址栏输入同样的请求不会出现跨域请求报错,但是通过js等前端页面Ajax请求会出现跨域请求报错?

因为 跨域是需要有“主语”的,也就是说,是“来自某一源的代码”访问“另一源的资源”,而在地址栏中输入的请求,其主语是用户,是“用户”在访问资源,而不是“某个本地域”在访问资源。

二、什么是跨域

当一个请求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)

三,本地模拟跨域请求

随便找个外网,F12后数据一下脚本 回车执行

$.ajax({
    url: "http://192.168.247.129:10000/edu/user/getUser",
    type: "get",
    contentType: "application/json",
    dataType: "json",
    success: function(data){
        if(data.msg="success"){
            alert("已提交成功");
        }else{
            alert("提交失败");
        }
    },
    error: function(data){
        alert("提交失败");
    }
});

四、解决跨域问题

首先思考解决跨域问题思路:

1.浏览器做限制:disable-web-security,禁止浏览器做跨域请求限制,这个不现实,不可能让所有客户端浏览器都做这种限制

2.JSONP:类似于把xhr请求包装成script资源来进行请求,前端dataType 改为 "jsonp" ,后端也要相应做出协议修改,不建议使用,很多时候不符合使用场景

3.接收方(服务端):

(1) 注入Filter配置跨域

package com.study.domainserver.config;

import com.study.domainserver.filter.AccessFilter;
import com.study.domainserver.intercepter.AccessIntercepter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.WebFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;


@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    /**
     *  ***********谈谈Filter和Intercepter的区别***********
     *  Web应用请求执行顺序:Filter=>Servlet=>Intercepter=>Controller
     *  1.Filter是Servlet容器(Tomcat)支持的,只能放在Java ee项目中;Intercepter是Spring容器支持的,既可以放在Java ee也可以放在Java se项目中
     *  2.Filter比Intercepter先执行
     *  3.Filter通过dochain放行,Interceptor通过prehandler放行。
     *  4.Filter只在方法前后执行,Interceptor粒度更细,可以深入到方法前后,异常抛出前后
     *  5.Interceptor可以获取IOC容器中的各个bean,而Filter就不行,这点很重要,在Interceptor里注入一个service,可以调用业务逻辑
     */

    /**
     * 设置允许跨域方法一:添加过滤器Filter
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean registFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new AccessFilter());
        return filterRegistrationBean;
    }

}
package com.study.domainserver.filter;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class AccessFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //允许跨域请求地址(*表示全部,但是无法满足带cookie请求,因为cookie只能在当前域请求)
        String requestOrigin = request.getHeader("origin");
        response.setHeader("Access-Control-Allow-Origin", requestOrigin);
        //允许接收cookie和发送cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");
        //允许请求的方法
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
        //允许请求头(Content-Type:请求数据/媒体类型 x-requested-with:判断请求是异步还是同步 自定义header 比如 token)
        String requestHeaders = request.getHeader("Access-Control-Request-Headers");
        response.setHeader("Access-Control-Allow-Headers", requestHeaders);
        //response.setHeader("Access-Control-Allow-Headers", "Content-Type,x-requested-with");
        //浏览器缓存请求头信息,1800秒内,只会有1次请求,不会出现"OPTIONS"预请求,节约资源
        //response.setHeader("Access-Control-Max-Age", "1800");
        filterChain.doFilter(request, response);
    }
}

(2) 注入Intercepter配置跨域

package com.study.domainserver.config;

import com.study.domainserver.filter.AccessFilter;
import com.study.domainserver.intercepter.AccessIntercepter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.WebFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;


@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    /**
     *  ***********谈谈Filter和Intercepter的区别***********
     *  Web应用请求执行顺序:Filter=>Servlet=>Intercepter=>Controller
     *  1.Filter是Servlet容器(Tomcat)支持的,只能放在Java ee项目中;Intercepter是Spring容器支持的,既可以放在Java ee也可以放在Java se项目中
     *  2.Filter比Intercepter先执行
     *  3.Filter通过dochain放行,Interceptor通过prehandler放行。
     *  4.Filter只在方法前后执行,Interceptor粒度更细,可以深入到方法前后,异常抛出前后
     *  5.Interceptor可以获取IOC容器中的各个bean,而Filter就不行,这点很重要,在Interceptor里注入一个service,可以调用业务逻辑
     */


    /**
     * 设置允许跨域方法二:添加拦截器Intercepter
     *
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AccessIntercepter()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }


}
package com.study.domainserver.intercepter;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

public class AccessIntercepter implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //允许跨域请求地址(*表示全部,但是无法满足带cookie请求,因为cookie只能在当前域请求)
        //允许跨域请求地址(*表示全部,但是无法满足带cookie请求,因为cookie只能在当前域请求)
        String requestOrigin = request.getHeader("origin");
        response.setHeader("Access-Control-Allow-Origin", requestOrigin);
        //允许接收cookie和发送cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");
        //允许请求的方法
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
        //允许请求头(Content-Type:请求数据/媒体类型 x-requested-with:判断请求是异步还是同步 自定义header 比如 token)
        String requestHeaders = request.getHeader("Access-Control-Request-Headers");
        response.setHeader("Access-Control-Allow-Headers", requestHeaders);
        //response.setHeader("Access-Control-Allow-Headers", "Content-Type,x-requested-with");
        //浏览器缓存请求头信息,1800秒内,只会有1次请求,不会出现"OPTIONS"预请求,节约资源
        response.setHeader("Access-Control-Max-Age", "1800");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

(3)Controller类上加@CrossOrigin注解

package com.study.domainserver.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/test")
@CrossOrigin
public class TestController {

    @GetMapping("msg")
    public Test testMsg() throws Exception {
        Test test = new Test();
        test.setId(1);
        test.setName("mike");
        System.out.println("/test/msg。。。。。。");
        return test;
    }
}

(4) Nginx配置服务端入口配置


#Nginx用户及组:用户 组。window下不指定
#user  nobody;

#工作进程:数目。根据硬件调整,通常等于CPU数量或者2倍于CPU。
worker_processes  1;

#错误日志:存放路径。
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid(进程标识符):存放路径
pid       /usr/local/nginx/logs/nginx.pid;

#一个进程能打开的文件描述符最大值,理论上该值因该是最多能打开的文件数除以进程数。
#但是由于nginx负载并不是完全均衡的,所以这个值最好等于最多能打开的文件数。
#LINUX系统可以执行 sysctl -a | grep fs.file 可以看到linux文件描述符。
worker_rlimit_nofile 65535;

events {
	#使用epoll的I/O 模型。linux建议epoll,FreeBSD建议采用kqueue,window下不指定。
	use epoll;
	
	#单个进程最大连接数(最大连接数=连接数*进程数)
    worker_connections  1024;
}


http {
	#设定mime类型,类型由mime.type文件定义
    include       mime.types;
	
    default_type  application/octet-stream;
	
	#服务器名字的hash表大小
    server_names_hash_bucket_size 128;
	
	#tcp_nodelay off 会增加通信的延时,但是会提高带宽利用率。在高延时、数据量大的通信场景中应该会有不错的效果
    #tcp_nodelay on,会增加小包的数量,但是可以提高响应速度。在及时性高的通信场景中应该会有不错的效果
	tcp_nodelay on;

    #长连接超时时间,单位是秒
    keepalive_timeout  65;
	
	server {
	    listen 80;
		server_name 192.168.247.130;
		location /{
			root html;
			index index.html index.htm;
			#解决跨域问题
            #允许跨域请求地址(*表示全部,但是无法满足带cookie请求,因为cookie只能在当前域请求)
            add_header Access-Control-Allow-Origin $http_origin;
            #允许接收cookie和发送cookie
            add_header Access-Control-Allow-Credentials 'true';
            #允许请求的方法
            add_header Access-Control-Allow-Methods 'GET,POST,DELETE,PUT,OPTIONS';
            #允许请求头(Content-Type:请求数据/媒体类型 x-requested-with:判断请求是异步还是同步 自定义header 比如 token)
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            #浏览器缓存请求头信息,1800秒内,只会有1次请求,不会出现"OPTIONS"预请求,节约资源
            #add_header Access-Control-Max-Age '1800';
		    if ($request_method = 'OPTIONS') {
                    return 204;
            }
		     proxy_pass http://192.168.247.130:8081;
		}  
	}
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值