前言

随着前后端分离以及微服务的应用越来越广,跨域与session保持问题已经是大家所熟知的问题,由于项目需要本周进行了相应资料的调研和测试,最终在客户端,前台,代理,服务端等多个层面上共同解决了这个问题,特别在此记录一下,希望能帮助大家。

为什么跨域

跨域来自于浏览器的安全基石,即”同源政策”(same-origin policy)。在浏览器看来,不同的服务端的域名或不同的端口不是同源的,即都是不受信任的,即要求协议,域名,端口全部相同。
具体如下所示。

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

而在实际项目或产品中,基本不可能与前台交互的都是一个源,或多或少的都会获取其他域的服务,因此就产生了常见的跨域问题。

怎么跨域

常见的跨域方法如下所示。

代理

采用Nginx的反向代理将前台的请求转发到后台,将前台的请求转变为应用程序到服务端的请求,曲线救国。

要达到这个目的需要部署nginx服务器和进行适当的配置,具体配置可参考如下博客

JSONP

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个

$.ajax({
   async:false,
   url: http://跨域的dns/document!searchJSONResult.action,
   type: "GET",
   dataType: 'jsonp',
   jsonp: 'jsoncallback',
   data: qsData,
   timeout: 5000,
   beforeSend: function(){
   //jsonp 方式此方法不被触发.原因可能是dataType如果指定为jsonp的话,就已经不是ajax事件了
   },
   success: function (json) {//客户端jquery预先定义好的callback函数,成功获取跨域服务器上的json数据后,会动态执行这个callback函数
    if(json.actionErrors.length!=0){
           alert(json.actionErrors);
     }
       genDynamicContent(qsData,type,json);
   },
    complete: function(XMLHttpRequest, textStatus){
    $.unblockUI({ fadeOut: 10 }); 
   },
   error: function(xhr){
    //jsonp 方式此方法不被触发.原因可能是dataType如果指定为jsonp的话,就已经不是ajax事件了
    //请求出错处理
    alert("请求出错(请检查相关度网络状况.)");
   }
});

或更简洁一点:

$.getJSON(" http://跨域的dns/document!searchJSONResult.action?name1="+value1+"&jsoncallback=?",
      function(json){
      if(json.属性名==值){
      // 执行代码
            }
        });

JSONP只能执行GET,其他的方法无法执行。

CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。参考

CORS还是存在一些限制,如下图所示
CORS的限制

原始的服务端需要比较大的改造。但目前大部分框架已经集成了相应的插件,使CORS的应用不再像以前那么困难。
比如SrpingBoot,只需要进行简单的改造即可

@Configuration
public class MyConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

具体方法请移步。

webSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
当然这个方法在跨域中并不常见,这里只是简单的提一下

当跨域遇到了Session

Session

Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。即服务端用于区分特定用户的数据结构。一般的做法都是在客户端通过验证后,服务端将一个sessionId告诉客户端,在随后的通讯中,客户端将sessionId告诉服务端,服务端通过验证sessionId的方法来确认客户端的身份。这里面的核心就是session是在服务端管理的,将id交给客户端用作区分。
因此在跨域时,需要在各个环节上都要保证客户端的sessionid不能丢失,否则就会被服务端拦截,无法获取服务的问题。

前台的Session保持

前台一般通过各种前后端交互框架实现从后台获取数据的,比如fetch,ajax,http等。下面就从这三个为例子简单介绍一下怎么保持session问题(实际上是携带cookie)。

Fetch

设置 credentials: “include”

getData: function (v,  callback, errorCallBack) {
    let url = URL + '/list?version='+v;
    fetch(url, {
        method: 'GET',
        credentials: "include"
    }).then((response)=>response.json())
        .then((responseJsonData)=> {
            callback && callback(responseJsonData);
        }).catch((error)=> {     
        });
},

jquery

一般需要采用全局设置的方式

$.ajaxSetup({xhrFiled:{
'withCredentials':true
}})
$.ajax(url:url,method:"get",success:function(data){console.log(data)});

Angular

设置{‘withCredentials’:true}

function get($scope,$http){
 $http.post(url,{v1:'name_eu'},{'withCredentials':true}).success(function(data){ $scope.var= data; }); }

客户端的session保持

这里的客户端指的是与服务端通讯的java,C#等单独的应用程序,实际上客户端本身是不存在纯粹的跨域问题,因为没有浏览器的同源策略的限制。但仍然存在session保持问题,同时由于不是前台,没有浏览器帮助解决cookie的携带问题,因此客户端的session保持问题需要对验证过程的更深入的理解才能完美解决。
在上文中我们已经谈到了实际上客户端(无论是前台还是应用程序)都是拿着sessionId向服务端进行验证的。我们通过浏览器开发工具可以看到,前端的sessionId是服务端写入到Cookie中的,而且一般是httpOnly属性(这也意味着我们不能通过普通的方法从cookie中将这个sessionId取出来),在向服务端发送请求时浏览器自动携带的,前台完成这一切只需要设置 credentials字段,其余的全部由浏览器或框架自动完成。但在客户端这一过程需要我们手动完成。下面我们就简单介绍以下,这一过程的基本原理。
1.向服务端发发送登录请求
2.在获取到服务端的反馈结果后,检查是否登录成功
3.若登录成功则获取该请求的cookie数据(JSESSIONID),并记录在缓存中。
4.按照业务需求发送其他请求,但要在每次发送时在request的header中添加Cookie,JESSIONID=缓存值(实际工程里我们可以检测每个cookie找到name=jsessionid的项,把整个项缓存起来)

值得注意的是,很多服务端都是有session过期的问题,因此需要更一段时间登录一次以保证session没有过期。

代理服务的设置

这里的代理服务器主要是nginx
在设置nginx要注意需要设置cookie的转发,否则这个session是保持不了的
在服务器设置部分

proxy_set_header Cookie $http_cookie

服务端的设置

最重要的当然是服务端的设置,否则一切都是白扯

header('Access-Control-Allow-Origin:http://127.0.0.1:80');  
header("Access-Control-Allow-Methods:HEAD,POST,GET,PUT,DELETE,OPTIONS"); 
header('Access-Control-Allow-Credentials: true');

注意由于设置了Access-Control-Allow-Credentials: true,因此必须设置Origin不能使用通配符(*)。

总结

跨域与Session保持问题涉及到系统的方方面面,从前台配置携带Cookie,客户端手动缓存和携带sessionId,反向代理服务器的设置到服务端CORS的配置,以及可能的JSONP的改造,虽然各方面技术都已经非常成熟,但涉及到细节众多,要彻底解决还是要消耗相当的精力。
但还有一个非常重要的问题那就是跨域产生的安全问题,需要大家额外注意,毕竟当安全有问题时,一切其他的问题就都不是问题了。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐