.net mvc webapi 处理跨域请求

来源:互联网 发布:淘宝网上怎么找货源 编辑:程序博客网 时间:2024/05/21 11:22

      现在流行webapp或者前端和后端分离,那么后端服务就会从重的程序处理,转变成数据驱动的数据抽取即可。那么webapi就变成了最佳选择。

然而,处理http请求还是仍旧的核心内容。 先看下跨域请求的定义:

    跨域资源共享-Cross Origin Resource Sharing(CORS)是一项W3C标准,允许服务端释放同源策略,使得服务端在接受一些跨域请求的同时拒绝其他的跨域请求( 浏览器的安全策略会阻止网页向另一个站点发送ajax请求,同时也会阻止恶意站点从另一个站点读取数据。这种限制被称作“同源策略”。)

     如果两个URL的协议、域名、端口相同,那么这两个URL就是同源。不是同源的就是跨域的,也就是说凡是发送请求URL的协议、域名、端口三者中任意一个与当前页面的URL不同即为跨域。

    看下CORS如何工作的

    CORS特性提供了多个Http请求头以供跨域请求使用。如果一个浏览器支持CORS,它会自动的为跨域请求加上相应的http请求头,在Http请求头的“Origin”项中是发起请求的站点域名。

      如果服务端允许了这个跨域请求,它会在http的Response的头部设置项Access-Control-Allow-Origin。只有当Response中Access-Control-Allow-Origin的值与Request中Origin的值相匹配时,这个请求才会成功。如果Access-Control-Allow-Origin的值是”*”,则意味着任意跨域访问都是被允许的。

       如果在HttpResponse的头部中没有包含Access-Control-Allow-Origin项,跨域请求会失败。即使服务端已经返回了正确的反馈,浏览器也不会将这个返回传递给客户端应用。

        对于一些CORS请求,浏览器会在发送实际需要的请求之前发送一个额外的请求,我们称之为“前置请求”。前置请求使用了Http Options方法,它包含了两个特殊的请求头Access-Control-Request-Method和Access-Control-Request-Headers。

也就是说浏览器发送了两次请求,首先发送前置请求,验证当前发送请求的站点是否在允许访问的域的范围内,若验证通过就发送真正的请求获取数据,否则请求就会被拒绝。在浏览器的调试界面(F12-Network)我们也可以看到请求是有两次。


在下列情况下,浏览器会跳过前置请求: 
1.Http请求方法是GET、HEAD或POST 
2.Http请求的Content-Type是application/x-www-form-urlencoded、multipart/form-data、text/plain 
3.Http请求头包含了Accept, Accept-Language, Content-Language, Content-Type

     理解了这个过程,和原理。下面写下如何实现:

1.如果指不限定谁能请求,谁都可以访问的话。

web.config文件中加入配置

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
        <add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS" />
      </customHeaders>
    </httpProtocol>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>

注意:当收不了Options请求时, <remove name="OPTIONSVerbHandler" /> 这句是个坑,去掉试试。

同时在 Global.asax里面 加入

       protected void Application_BeginRequest()
        {
            if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
            {
                Response.End();
            }
        }

这两处修改意思很明确,第一个增加配置,服务器会在response的头部加入Access-Control-Allow3项内容,浏览器看到他们就完成交互了。第二处意思是看见options的前置请求并且就直接结束,接着处理下面正式的请求。


2 如果要限定哪些域名可以访问的话,如果是vs2013以上的可以直接通过NuGet安装Microsoft.AspNet.WebApi.Cors为WebApi添加CORS。使用[EnableCros]属性可以全局设置CORS,也可以为Controller或Action单独设置CORS。

但是,我的开发vs是2013版本的,引入该插件最新版本,项目即可出现dll版本不适配的情况,导致不能正常启动运行。

mvc4 不能使用,mvc5 可使用。


3 最后的方法只能根据原理,重写相关方法,来实现了。

由于跨域是2次请求。先处理前置请求,然后处理正式请求。浏览器发来的请求如下

{Method: OPTIONS, RequestUri: 'http://192.168.10.112/TempsenCloudApi/api/CompanyAccount/CreatTenant', 
Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, 
Headers:
{
  Connection: keep-alive
  Accept: */*
  Accept-Encoding: gzip
  Accept-Encoding: deflate
  Accept-Encoding: sdch
  Accept-Language: zh-CN
  Accept-Language: zh; q=0.8
  Host: 192.168.10.112
  Referer: http://192.168.10.104:8080/
  User-Agent: Mozilla/5.0
  User-Agent: (Windows NT 6.1; Win64; x64)
  User-Agent: AppleWebKit/537.36
  User-Agent: (KHTML, like Gecko)
  User-Agent: Chrome/58.0.3029.110
  User-Agent: Safari/537.36
  Access-Control-Request-Method: POST
  Origin: http://192.168.10.104:8080
  Access-Control-Request-Headers: content-type
}}

首先重写CorsPreflightActionSelector : ApiControllerActionSelector类的SelectAction方法。

public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
        {
            var originalRequest = controllerContext.Request;
            var isCorsRequest = originalRequest.Headers.Contains(Headers.Origin);

            if (originalRequest.Method == HttpMethod.Options && isCorsRequest)
            {
                var accessControlRequestMethod = originalRequest.Headers.GetValues(Headers.AccessControlRequestMethod).FirstOrDefault();
                if (!string.IsNullOrEmpty(accessControlRequestMethod))
                {
                    var modifiedRequest = new HttpRequestMessage(
                        new HttpMethod(accessControlRequestMethod),
                        originalRequest.RequestUri
                        );
                    controllerContext.Request = modifiedRequest;
                    HttpActionDescriptor actualDescriptor = base.SelectAction(controllerContext);
                    controllerContext.Request = originalRequest;

                    if (actualDescriptor != null && actualDescriptor.GetFilters().OfType<CorsEnabledAttribute>().Any())
                    {
                        return new CorsPreflightActionDescriptor(actualDescriptor, accessControlRequestMethod);
                    }
                }
            }
            return base.SelectAction(controllerContext);
        }

然后再继承CorsEnabledAttribute : ActionFilterAttribute ,控制访问的controller和action

注意几种方法选其一,不能混合使用。也就是web.config里的配置要去掉,选择后两种方式时。



原创粉丝点击