Django与React 前后端分离开发时遇到CORS跨域问题导致的Session无法传递的问题

网上关于如何解决Django的跨域问题的文章有很多;
一般来说,参考django解决跨域请求的问题 - AFei0018-博客 - CSDN博客这篇文章,装一下django-cors-headers,settings.py里设置一下中间件即可解决。
但当和前端结合在一起工作时,解决跨域还需要前端的协助。以使用React为例,如果是用ajax发请求,可以参考这篇Django配置Ajax跨域调用/设置Cookie - 孔天逸’Blog - CSDN博客。
但仅仅设置后端是不够的。当前后端通信时仍然会遇到cookie为空的情况。
我的开发场景为React+axios,通过搜索了解到为了解决cookie的问题,使用axios时,要在import axios的时候,设置withCredentials参数为true(aixos默认为false),即:

axios.defaults.withCredentials=true;

这样前端才能正常地发送cookie到后端。

但到这一步,虽然cookie可以被后端正常收到,但是Django自带的session却始终为空。
我们知道session的实现是依赖于cookie的, 简单来说,session的数据存储在服务器(如Django链接的数据库)中,服务器通过cookie将session id传到前端,前端再向请求后端发请求时会同时发回session id,这样经过Django的中间件的处理,我们可以方便地在view中使用session作权限管理等。但是在本场景下,当前端服务器和后端服务器不在同一主机(ip)时,session中的内容始终无法正常地被后端访问到,session始终为空。我搜遍了各种解决方法,几乎遍历式地尝试了各种设置,始终不能成功。经过手动在cookie中设置“sessionid”字段,发现原因应该为Django的中间件会直接过滤掉该字段,从而导致客户端无法保存sessionid,进而在之后客户端向服务端发送url请求时,无法把正确的sessionid发给服务端,自然Django的中间件也无法获得对应的session数据了。
而过滤的原因是因为Django的SESSION_COOKIE_DOMAIN字段限制了session的cookie的作用域,使得session id只能局限于服务器本地网中,我们可以通过改变这个参数来改变作用域,但Django的这个参数只能指定一个域名。为了以后拓展的方便,我手动在cookie中添加了一个"session_id"参数,手动指定的参数可以被传递给各个网址。然后前端再从response中提取这个参数,保存到前端的cookie中,这样后端就可以正常访问cookie,从而可以正常从数据库中取出对应的session。

前端代码示例:

import axios from 'axios'
import {LOGIN_EVENT, FETCH_RECS, SET_FAVORITES} from './types'

// axios.defaults.xsrfCookieName = "csrftoken";
// axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.withCredentials=true;


function setCookie(name,value) {
    var days = 14;
    var exp = new Date();
    exp.setTime(exp.getTime() + days*24*60*60*1000);
    document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString()+"; path=/";
}
export function loginAction(data){
  return function(dispatch){
    axios.post('http://xxx.xxx.xxx.xxx/login/',{
        username:document.getElementById('username').value,
        password:document.getElementById('password').value,
        login_time:Date.now()
      }).then(res => {
        if(res.data.status==="success"){
          setCookie("sessionid", res.data.session_id)
          let userInfo = res.data.data
        }
      else{
        alert('登录出现错误');
      }
    }).catch(e=> {
      alert('登录出现错误(' + e + ')')
    })
  }
}

后端代码示例:
添加session key到cookie中:

session_id =request.session.session_key
            print('session id in login request: '+str(session_id))
            ....
            ## 注意下面是response 不是request;客户端浏览器能从cookie中读到我们的session_id
            response.set_cookie('session_id',session_id)

使用session key 读取数据库中的session:

from django.contrib.sessions.models import Session

...


session_id =request.COOKIES['session_id']
        print("sid in request in feeds:"+str(request.COOKIES['session_id']))
        sess = Session.objects.get(pk=session_id)
        sess_data = sess.get_decoded() #sess_data是一个字典

前后花了整整一天,被这种前后端结合的bug折磨得有点惨,最后算是搞清楚了cookie和session的发送与存储方式,事实证明在解决复杂bug时,如果搜不到类似案例,还是读源码最有效。

另外还要感谢这几篇文章的作者!
django中的session实现 - 知乎
前后端分离实践(django + vue) · ckdk’s blog

你可能感兴趣的:(后端开发)