CROS与其安全问题处理

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.

  

原文地址:https://www.cnblogs.com/ShouWangYiXin/p/13037975.html