前端跨域&iOS端 universal link相关

什么是跨域?为什么会出现跨域问题?

出于浏览器的同源策源限制。同源策略是一种约定,它是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会收到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

当一个请求的url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

跨域请求本是一个不正常的操作,但是在某些必要的条件下,不得已才进行跨域请求。

什么是universal link?

Universal Link(通用链接)是Apple在iOS9推出的一种能够方便的通过传统HTTPS链接来启动APP的功能,可以通过访问指定的网址拉起APP。当你的应用支持Universal Link(通用链接),当用户点击一个链接是可以跳转到你的网站并获得无缝重定向到对应的APP,且不需要通过Safari浏览器。如果你的应用不支持的话,则会在Safari中打开该链接。如图所示,在Safari中打开一个o2o.dailyyoga.com.cn域名的h5网页,最上面会出现APP的相关介绍,以及一个打开按钮,点击打开,直接拉起APP。

当然,这里有个坑,就是网页中在跨域访问页面(比如通过window.location.href跳转指定的web页面),会自动触发universal link直接拉起app,例:从h5.dailyyoga.com.cn的域名网页跳转到 o2o.dailyyoga.com.cn的域名网页,则会拉起app,非人为可控。

universal link 都是针对ios端,与android无关。

问题出现背景

基本概念介绍完了,接下来就介绍一下问题出现背景。

不确定哪个版本ios端添加了universal link的配置,做业务需求时发现此问题。

公司有个项目(以下简称A项目)的域名在做了服务端渲染重构之后,从o2o.dailyyoga.com.cn修改为了node.dailyyoga.com.cn。分享到微信时,在登录页,使用微信登录。通过href跳转 authorizeWeiXin接口,然后在服务器端走整套微信授权的流程。

window.location.href = "https:o2o.dailyyoga.com.cn/xxx/xxx/xxx/authorizeWeiXin?call_back_url=" + base64.encode(encodeURI(backUrl)) + '&state=challenge';

这里原网页 node.dailyyoga.cm.cn 跳转 o2o.dailyyoga.com.cn的域名,跨域访问。触发universal link机制,自动拉起app,无法在微信端完成正常微信登录。此问题当时通过配置nginx代理的方式,将o2o的域名转发到node的域名下,这样,o2o.dailyyoga.com.cn/o2_detail就可以正常访问 node.dailyyoga.com.cn/o2_detail对应目录下的资源文件了。所以分享到外部时,链接地址使用o2o的域名(注意:同源访问不会自动触发universal link,而拉起app),当时这个问题就算是解决了。

当然,后来又有了新的问题。A项目这边有短信推送的需求,o2o域名的链接,ios端,在短信里面点击,不会跳到网页,而是直接拉起了app(其实在备忘录里面点击o2o的域名也会直接拉起app)。

至此,没办法了,ios端添加的universal link配置不能去掉,所以h5端只能换个新域名了,h5.dailyyoga.com.cn域名就诞生了。

以后通过短信推广活动时,web端网页就用h5.dailyyoga.com.cn的域名。

跨域请求的解决方法

h5.dailyyoga.com.cn这个域名可以访问o2o.dailyyoga.com.cn域名下的所有项目资源。在请求访问o2o域名下的接口时就跨域了。

1. 普通的跨域请求(如:不带cookie验证)

    只需要服务器端设置Access-Control-Allow-Origin,可以指定域名跨域访问,也可以设置 * 允许所有域名请求。

2. 带cookie跨域请求:前后端都需要进行设置(我们目前线上接口都是带cookie验证的)

    【前端设置】 根据xhr.withCredentials字段判断是否带有cookie

     ① 原生ajax

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
 
// 前端设置是否带cookie
xhr.withCredentials = true;
 
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
 
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
};

    ② jQuery ajax

$.ajax({
   url: 'http://www.test.com:8080/login',
   type: 'get',
   data: {},
   xhrFields: {
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
});

    ③ vue-resource

Vue.http.options.credentials = true

    ④ axios

axios.defaults.withCredentials = true

    ⑤ angularjs $http

$http({
        method: "POST || GET",
        url: 接口url,
        headers: {},
        data: data, // 参数
        withCredentials: true
    }).then(
        function successCallback(rsp) {},
        function errorCallback(rsp) {}
    );

本次修改是在之前服务端大佬做接口校验时,添加的core.js文件(代理了项目中的请求,添加了一些request header),所有的web端项目访问都会异步访问该js文件。所以在core.js中添加  xhr.withCredentials = true;即可在前端所有项目中生效此设置。

    【服务端设置】

     服务器端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 
 
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true"); 
 
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

    需要注意的是,服务器端Access-Control-Allow-Credentials = true时,Access-Control-Allow-Origin的值不能为 * 号,应设置为发起请求的地址。所以服务器在设置  Access-Control-Allow-Origin时,一般会取request header 中的 origin字段。

至此,跨域的问题就完美解决了。

微信授权的问题

    如前所述,h5在微信登录时,通过window.location.href跳转,完成授权访问。然而href跳转o2o域名会拉起app。所以当时尝试了href到公网ip去尝试授权,可以授权成功,但是微信有ip访问时的提示页面,很不友好。

    所以,最后我们把之前服务器的一部分逻辑放到了前端实现,即由前端直接访问 微信的授权地址

window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=*********&redirect_uri=https://o2o.dailyyoga.com.cn/xxx/xxx/xxx/authorizeWeiXin?call_back=' + base64.encode(encodeURI(backUrl)) + '&response_type=code&scope=snsapi_userinfo&state=challenge'

至此,微信登录拉起app的问题也完美解决。

补充:preflight request(预检请求)

    在正式跨域之前,浏览器会根据需要发起一次预检(也就是options请求),用来让服务端返回允许的方法(如get 、post),被跨域访问的Origin(来源或者域),还有是否需要Credentials(认证信息)等。

    浏览器将CORS请求分为两类:简单请求(simple request) 和非简单请求(not-simple-request),简单请求浏览器不会预检,而非简单请求会预检。

    同时满足下列三大条件,就属于简单请求,否则属于非简单请求。

    1. 请求方式只能是:GET、POST、HEAD

    2. HTTP 请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID

    3. Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain

    发起预检请求时,浏览器会先询问服务器,当前网页所在域名是否在服务器的许可白名单之中,服务器允许之后,浏览器会发出正式的XMLHttpRequest请求,否则会报错。所以这里一般需要服务器端实现拦截器,对Options请求做单独对应的处理。

    下面看个例子

      

    该请求为get请求,但是由于 Request Header中有自定义的验证信息 'xxx-xxx-type',所以就变成了 '复杂请求',先发起了options请求,需要注意的是,preflight request请求并不会带上自定义的验证信息(这里就是'xx-xxx-type'和cookie等)。

猜你喜欢

转载自blog.csdn.net/longgege001/article/details/114547676