一、为什么会出现跨域问题
出于浏览器的同源策略限制。同源策略(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;
}
}
}