网页中的跨域请求 同源策略、跨域解决方案

品习知识点

简单表述几个概念,详解@度娘。

1、同源策略,浏览器最核心的安全功能,在无授权情况下,只允许读写相同源的资源。其中源(Origin)指的是协议、域名、接口,同源即三者相同。

2、预检请求,浏览器出于安全策略,在跨域请求数据时候预先发起请求,以知是否可跨域请求数据的请求。

关于以上两个知识点,推荐参考以下几篇文章,写得很好。

CORS简介

同源策略、跨域解决方案

Ajax跨域、Json跨域、Socket跨域和Canvas跨域等同源策略限制的解决方法

预检请求(preflight request)

浅谈关于预检请求


针对跨域请求的问题,会有很多种解决办法。下面,我提供个人用的两种办法。

第一种解决方案,是非常简单的

第一次遇到跨域的问题,我用了非常简便的方法:在 Web.config 配置文件中添加配置。配置如下,

    <!--允许跨域 开始-->  
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="http://domain.testweb.cn:18833" />
        <add name="Access-Control-Allow-Credentials" value="true" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
        <add name="Content-Security-Policy" value="upgrade-insecure-requests" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
      </customHeaders>
    </httpProtocol>
    <!--允许跨域 结束-->

在节点 <customHeaders> 中配置可跨域请求的来源:<add name="Access-Control-Allow-Origin" value="http://domain.testweb.cn:18833" />

如此一来,以上配置的源请求便可在浏览器中直接访问该站点的资源。

这个解决办法,简洁明了,美观大方,直接暴力,方便有效。

但,也有个弊端,Access-Control-Allow-Origin 的值只能设置一个源 or 任何源(设置 * 号值表示任何源),设置 * 号则对所有请求一视同仁了。


第二种解决方案,是相当灵活的

第二次遇到跨域的问题,业务要求就比第一次的复杂一些。要求针对特定的一些源以及部分被访问的数据接口设置可跨域请求。

以上第一种解决跨域的方案满足不了这样的需求。我们用到了以下这种解决办法。

在 Global.asax.cs 中拦截到请求,对访问的请求作判断和处理。

        #region 请求响应处理
        /// <summary>
        /// 请求响应处理
        /// </summary>
        protected void Application_BeginRequest()
        {
            List<IfCrossDomainAccess> infCrossAccess = KeysHelper.GetIfCrossDomainAccess();
            if (infCrossAccess != null && infCrossAccess.Count > 0)
            {
                string requestInf = Request.Url.AbsolutePath.ToLower();
                string requestOrigin = Request.Headers["Origin"] == null ? string.Empty : Request.Headers["Origin"].ToString().ToLower();
                List<string> inf = infCrossAccess.Select(p => p.Interface).ToList();
                if (inf.Any(p => !string.IsNullOrWhiteSpace(p) && p == requestInf)) //接口有允许跨域
                {
                    IfCrossDomainAccess inft = infCrossAccess.FirstOrDefault(p => p.Interface == requestInf);
                    if (inft != null && inft.CrossDomain != null && inft.CrossDomain.Count > 0)
                    {
                        CrossDomain domain = inft.CrossDomain.FirstOrDefault(pp => pp.Domain == requestOrigin); //请求来源域名允许访问
                        if (domain != null && !string.IsNullOrWhiteSpace(domain.Method))
                        {
                            Response.Headers.Add("Access-Control-Allow-Origin", domain.Domain);
                            Response.Headers.Add("Access-Control-Allow-Credentials", "true");
                            Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
                            Response.Headers.Add("Content-Security-Policy", "upgrade-insecure-requests");
                            Response.Headers.Add("Access-Control-Allow-Methods", domain.Method);
                        }
                    }
                }
            }
            if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS") Response.End(); //Preflighted Requests(预检请求) 处理
        }
        #endregion

获取跨域配置

        #region 获取允许跨域访问的接口
        /// <summary>
        /// 获取允许跨域访问的接口
        /// </summary>
        /// <returns></returns>
        public static List<IfCrossDomainAccess> GetIfCrossDomainAccess()
        {
            List<IfCrossDomainAccess> inf = new List<IfCrossDomainAccess>();
            try
            {
                IfCrossDomainAccess infCross = new IfCrossDomainAccess();
                if (!File.Exists(CurrentConfig)) return inf;
                XmlDocument xml = new XmlDocument();
                xml.Load(CurrentConfig);
                XmlNode xNode = xml.SelectSingleNode("//accessAllowSettings");
                if (xNode == null) return inf;
                XmlNodeList infNodes = xNode.SelectNodes("//interface");
                if (infNodes != null && infNodes.Count > 0)
                {
                    foreach (XmlNode n in infNodes)
                    {
                        infCross = new IfCrossDomainAccess();
                        infCross.CrossDomain = new List<CrossDomain>();
                        infCross.Interface = n.Attributes["value"].Value.ToLower();
                        XmlNodeList origin = n.ChildNodes;
                        if (origin != null && origin.Count > 0)
                        {
                            foreach (XmlNode o in origin)
                            {
                                infCross.CrossDomain.Add(new CrossDomain
                                {
                                    Domain = o.Attributes["value"].Value.ToLower(),
                                    Method = o.Attributes["method"].Value
                                });
                            }
                        }
                        inf.Add(infCross);
                    }
                }
            }
            catch (Exception ex){
                LogHelper.Error("获取允许跨域接口【KeysHelper.GetIfCrossDomainAccess】出错:" + ex);
                return inf;
            }
            return inf;
        }
        #endregion
View Code

跨域配置文件:

代码逻辑思路:

1、获取到配置的 允许跨域访问的源和接口;

2、获取请求头部中的源和请求接口;

3、匹配检查所请求的接口是否有允许跨域,若有,则再检查请求的来源是否允许跨域访问,若有,则在响应头部中,添加一下属性,

Access-Control-Allow-Origin,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Content-Security-Policy,Access-Control-Allow-Methods,

4、判断当前请求是否是预检请求,若是,停止请求接口数据,将请求的响应返回。

以上步骤,步骤1 是重要的一部分,可设计动态配置可跨域访问。步骤4 涉及到预检请求的处理,尤为关键。


跨域请求的信息截图

跨域请求无跨域允许,浏览器的报错信息:

跨域请求有得到允许,访问的情况:

 


author:韦小明

本文路径:http://www.cnblogs.com/youler/p/9815736.html


猜你喜欢

转载自www.cnblogs.com/youler/p/9815736.html