1、CORS定义:
CORS(Cross-Origin Resource Sharing, 跨源资源共享)是W3C出的一个标准,其思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。当一个请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。因此,要想实现CORS进行跨域,需要服务器进行一些设置,同时前端也需要做一些配置和分析。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。(并不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。)
安全问题:如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。比如下面的操作就有安全问题:
1)用户访问www.mybank.com ,登陆并进行网银操作,这时cookie啥的都生成并存放在浏览器
2)用户突然想起件事,并迷迷糊糊地访问了一个邪恶的网站 www.xiee.com
3)这时该网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对www.mybank.com 的操作。
如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。
既然有安全问题,那为什么又要跨域呢? 有时公司内部有多个不同的子域,比如一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域。
2、CORS问题处理
如果能够控制住允许某些ip访问特定的资源,这样貌似既可以享受跨域的好处,又可以避免安全问题,下面是一次实验,请各位指正:
1.后台接口代码,有两个方法,两个区别只是返回内容不同,格式一致 package com.xbrother.report.custom.controller; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; @Controller @RequestMapping("tc") public class TestCorsController { @ResponseBody @RequestMapping("tm1") public String testMethod1(HttpServletRequest request, HttpServletResponse response){ Enumeration<String> headerNames = request.getHeaderNames(); System.out.println("请求时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println("请求接口:tm1"); Map<String, String> map = new HashMap<>(); map.put("tm", "tm1"); while (headerNames.hasMoreElements()) { String headerName = (String) headerNames.nextElement(); String value = request.getHeader(headerName); map.put(headerName, value); System.out.println("请求头-"+headerName + ":"+value); } return JSON.toJSONString(map); } @RequestMapping("tm2") public String testMethod2(HttpServletRequest request, HttpServletResponse response){ Enumeration<String> headerNames = request.getHeaderNames(); System.out.println("请求时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println("请求接口:tm2"); Map<String, String> map = new HashMap<>(); map.put("tm", "tm2"); while (headerNames.hasMoreElements()) { String headerName = (String) headerNames.nextElement(); String value = request.getHeader(headerName); map.put(headerName, value); System.out.println("请求头-"+headerName + ":"+value); } return JSON.toJSONString(map); } } 2.将上面的代码部署到3.27 3.浏览器直接访问接口tm1的url 1)后台打印日志如下: 请求时间:2020-06-01 15:23:45 请求接口:tm1 请求头-host:192.168.3.27:4143 请求头-connection:keep-alive 请求头-cache-control:max-age=0 请求头-upgrade-insecure-requests:1 请求头-user-agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 请求头-accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 请求头-accept-encoding:gzip, deflate 请求头-accept-language:zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 请求头-cookie:MODE=0; X_GU_SID=XSS_FpplS7-OOb9L5mdBUqmiLdD-rASGm6AqjO71H2cbhnxNCv4; USER_ID=1; ACCOUNT=YWRtaW4=; USER_NAME=57O757uf566h55CG5ZGY; THEME=default; X_PRODUCT=gu; JSESSIONID=dummy 2)浏览器收到的响应如下: { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-encoding": "gzip, deflate", "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "max-age=0", "connection": "keep-alive", "cookie": "MODE=0; X_GU_SID=XSS_FpplS7-OOb9L5mdBUqmiLdD-rASGm6AqjO71H2cbhnxNCv4; USER_ID=1; ACCOUNT=YWRtaW4=; USER_NAME=57O757uf566h55CG5ZGY; THEME=default; X_PRODUCT=gu; JSESSIONID=dummy", "host": "192.168.3.27:4143", "tm": "tm1", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" } 3.编写如下的跨域访问html/js代码,部署到3.207和 <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>Test</title> <script src="../js/jquery.min.js"></script> </head> <body> <div class="divs" id="div1"></div><button class="bts" onclick="cors1()">div1</button> <div class="divs" id="div2"></div><button class="bts" onclick="cors2()">div2</button> </body> <script type="text/javascript"> var url1 = "http://192.168.3.27:4143/sb/demo/tc/tm1"; var url2 = "http://192.168.3.27:4143/sb/demo/tc/tm2"; function cors(divid,url,method) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { debugger; if (this.readyState == 4 && this.status == 200) { document.getElementById(divid).innerHTML = this.responseText; } }; xhttp.open(method, url, true); xhttp.withCredentials = true;//浏览器跨域请求默认不携带cookie,要想携带跨域的cookie,必须配置此配置 xhttp.send(); } function cors1() { cors("div1",url1,"get") } function cors2() { cors("div2",url2,"put") } </script> </html> 4.浏览器访问3.207的html,点击div1按钮通过其中的js代码进行跨域访问 1)后台打印日志如下: 请求时间:2020-06-01 15:41:06 请求接口:tm1 请求头-host:192.168.3.27:4143 请求头-connection:keep-alive 请求头-origin:http://192.168.3.207 请求头-user-agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 请求头-accept:*/* 请求头-referer:http://192.168.3.207/xbreport/view/page/test_cors.html 请求头-accept-encoding:gzip, deflate 请求头-accept-language:zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 2)浏览器没有响应显示 响应中提示 This request has no response data available. 控制台提示以下错误: Access to XMLHttpRequest at 'http://192.168.3.27:4143/sb/demo/tc/tm1' from origin 'http://192.168.3.207' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 5.在3.27的工程中加入以下的过滤器 package com.example.demo1.t1; import java.io.IOException; import java.util.ArrayList; import java.util.List; 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; import org.springframework.stereotype.Component; /** *通过注解添加一个过滤器,并指定过滤器的名称和过滤的URL规则 */ @Component @WebFilter(filterName="CORSFilter",urlPatterns="/*") public class CORSFilter implements Filter { public void init(FilterConfig filterConfig) throws ServletException { } //0.准备一个允许访问的资源列表和允许跨域的IP列表,在后台程序启动时加载 //允许跨域访问的接口 List<String> allowUri = new ArrayList<String>(); { allowUri.add("/sb/demo/tc/tm1"); } //允许跨域访问的IP List<String> allowIp = new ArrayList<String>(); { allowIp.add("192.168.3.207"); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; HttpServletRequest request = (HttpServletRequest) servletRequest; //1.先获取请求的来源IP,验证此IP是否在允许跨域的IP范围内 //这里origin是带协议的,可以在后面判断中通过一些处理忽略协议 String origin = request.getHeader("Origin"); if(origin == null){ origin = request.getHeader("Referer"); } if(testOrigin(origin)){ //2.先获取要访问的资源路径,验证是否是允许跨域访问的资源 String uri = request.getRequestURI(); if(allowUri.contains(uri)){ //3.设置跨域相关参数 //服务器是否允许跨域与三个参数有关:Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Credentials,其中前两个是必须的 //是否允许携带cookie,这个配置网络上说有关,但是在这设置貌似没什么作用,真正起到配置cookie作用的是js中的 xhttp.withCredentials = true 这一配置 response.setHeader("Access-Control-Allow-Credentials", "true"); //允许的origin,可以设置为*,但是如果设置Access-Control-Allow-Credentials为true,则不能再设置为* //response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Origin", origin); //允许的请求方法 response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); }else{ //如果不是,可以做些其他操作,这里直接抛出异常 throw new RuntimeException("收到跨域请求攻击,请求来自" + origin + ",请求资源:" + uri); } }else{ //如果不是,可以做些其他操作 System.out.println("收到跨域请求攻击,请求来自" + origin + ",跨域IP验证未通过"); } filterChain.doFilter(request, servletResponse); } public void destroy() { } private boolean testOrigin(String origin){ for (String ip : allowIp) if(origin.indexOf(ip) > -1) return true; return false; } } 6.浏览器访问3.207的html,点击div1按钮通过其中的js代码进行跨域访问 1)后台打印日志 请求时间:2020-06-01 16:15:39 请求接口:tm1 请求头-host:192.168.3.27:4143 请求头-connection:keep-alive 请求头-origin:http://192.168.3.207 请求头-user-agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 请求头-accept:*/* 请求头-referer:http://192.168.3.207/xbreport/view/page/test_cors.html 请求头-accept-encoding:gzip, deflate 请求头-accept-language:zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 请求头-cookie:MODE=0; X_GU_SID=XSS_FpplS7-OOb9L5mdBUqmiLdD-rASGm6AqjO71H2cbhnxNCv4; USER_ID=1; ACCOUNT=YWRtaW4=; USER_NAME=57O757uf566h55CG5ZGY; THEME=default; X_PRODUCT=gu; JSESSIONID=dummy 2)浏览器收到响应: { "accept": "*/*", "accept-encoding": "gzip, deflate", "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "connection": "keep-alive", "host": "192.168.3.27:4143", "origin": "http://192.168.3.207", "referer": "http://192.168.3.207/xbreport/view/page/test_cors.html", "tm": "tm1", "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36", "cookie":"MODE=0; X_GU_SID=XSS_FpplS7-OOb9L5mdBUqmiLdD-rASGm6AqjO71H2cbhnxNCv4; USER_ID=1; ACCOUNT=YWRtaW4=; USER_NAME=57O757uf566h55CG5ZGY; THEME=default; X_PRODUCT=gu; JSESSIONID=dummy" } 7.浏览器访问3.207的html,点击div2按钮通过其中的js代码进行跨域访问 1)后台打印日志 六月 01, 2020 4:17:29 下午 org.apache.catalina.core.StandardWrapperValve invoke 严重: Servlet.service() for servlet [dispatcherServlet] in context with path [/sb/demo] threw exception java.lang.RuntimeException: 收到跨域请求攻击,请求来自http://192.168.3.207,请求资源:/sb/demo/tc/tm2 at com.example.demo1.t1.CORSFilter.doFilter(CORSFilter.java:62) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) 2)浏览器报错; test_cors.html:39 OPTIONS http://192.168.3.27:4143/sb/demo/tc/tm2 500 test_cors.html:1 Access to XMLHttpRequest at 'http://192.168.3.27:4143/sb/demo/tc/tm2' from origin 'http://192.168.3.207' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.