服务跨域以及Session保持问题

前言

随着前后端分离以及微服务的应用越来越广,跨域与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的改造,虽然各方面技术都已经非常成熟,但涉及到细节众多,要彻底解决还是要消耗相当的精力。
但还有一个非常重要的问题那就是跨域产生的安全问题,需要大家额外注意,毕竟当安全有问题时,一切其他的问题就都不是问题了。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
class Session { //mysql的主机地址 const db_host = "localhost"; //需要第三方指定ip地址 //数据库用户名 const db_user = "root"; //需要第三方指定自己的用户名 //数据库密码 const db_pwd = ""; //需要第三方指定自己的库据库密码 //数据库 const db_name = "thinkphp"; //需要第三方指定数据库 //数据库表 const db_table = "tbl_session"; //需要第三方指定数据表 //mysql-handle private $db_handle; //session-lifetime private $lifeTime; function open($savePath, $sessName) { // get session-lifetime $this--->lifeTime = get_cfg_var("session.gc_maxlifetime"); // open database-connection $db_handle = @mysql_connect(self::db_host, self::db_user, self::db_pwd); $dbSel = @mysql_select_db(self::db_name, $db_handle); // return success if(!$db_handle || !$dbSel) return false; $this->db_handle = $db_handle; return true; } function close() { $this->gc(ini_get('session.gc_maxlifetime')); // close database-connection return @mysql_close($this->db_handle); } function read($sessID) { // fetch session-data $res = @mysql_query("SELECT session_data AS d FROM ".self::db_table." WHERE session_id = '$sessID' AND session_expires > ".time(), $this->db_handle); // return data or an empty string at failure if($row = @mysql_fetch_assoc($res)) return $row['d']; return ""; } function write($sessID, $sessData) { // new session-expire-time $newExp = time() + $this->lifeTime; // is a session with this id in the database? $res = @mysql_query("SELECT * FROM ".self::db_table." WHERE session_id = '$sessID'", $this->db_handle); // if yes, if(@mysql_num_rows($res)) { // ...update session-data @mysql_query("UPDATE ".self::db_table." SET session_expires = '$newExp', session_data = '$sessData' WHERE session_id = '$sessID'", $this->db_handle); // if something happened, return true if(@mysql_affected_rows($this->db_handle)) return true; } else // if no session-data was found, { // create a new row @mysql_query("INSERT INTO ".self::db_table." ( session_id, session_expires, session_data) VALUES( '$sessID', '$newExp', '$sessData')", $this->db_handle); // if row was created, return true if(@mysql_affected_rows($this->db_handle)) return true; } // an unknown error occured return false; }
Spring Boot 跨域Session Cookie失效问题的解决方法如下: 首先,跨域问题可以通过配置Spring Boot的CORS(跨源资源共享)来解决。在Spring Boot中,可以使用注解 `@CrossOrigin` 或在配置类中添加 `addCorsMappings` 方法来配置跨域的访问。 @CrossOrigin 注解可以应用在控制器类或方法上,指定允许跨域的来源、方法、头部、是否允许携带凭证(比如 Cookie)等参数。例如: ```java @CrossOrigin(origins = "http://localhost:8080", maxAge = 3600, allowCredentials = "true") @GetMapping("/example") public ResponseEntity<String> getExample() { // ... } ``` 另一种配置跨域的方法是创建配置类,并继承 `WebMvcConfigurer` 接口,并重写其 `addCorsMappings` 方法。例如: ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST") .allowCredentials(true) .maxAge(3600); } } ``` 其次,Session Cookie失效问题可以通过在跨域请求中添加凭证(Credentials)来解决。具体来说,可以将 `allowCredentials` 参数设置为 `true`,同时在请求头中添加 `withCredentials: true`。例如: ```javascript fetch('http://localhost:8080/api/example', { method: 'GET', credentials: 'include' // 或 'same-origin' }) ``` 这样配置后,Spring Boot就可以正常接收带有 Cookie 的跨域请求,并在服务保持 Session 的有效性。 综上所述,通过配置跨域设置和同时在请求中添加凭证,可以解决Spring Boot跨域Session Cookie失效的问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值