Django-Rest-Framework sessionid 和 csrftoken 未在 Chrome 上设置

Posted

技术标签:

【中文标题】Django-Rest-Framework sessionid 和 csrftoken 未在 Chrome 上设置【英文标题】:Django-Rest-Framework sessionid and csrftoken aren't set on Chrome 【发布时间】:2014-10-03 02:48:53 【问题描述】:

我有一个简单的 Django(Rest Framework)应用程序。我已经正确启用了 CSRF、CORS、Session 中间件。我尝试调试使用 Backbone 编写的前端 UI,并且 sessionid 和 csrftoken 不在浏览器的持久存储中。 更让我困惑的是,当我注销时,我收到了匿名用户的 sessionid(没有 csrftoken 对),并且该 cookie 被持久化。

我使用谷歌浏览器。症状:

当我执行登录时,我在响应中收到两个令牌的 Set-Cookie 标头 令牌有不同的到期日期 令牌出现在 chrome 的响应 cookie 选项卡中,但不在 cookie 存储中 如果我注销,我会收到匿名用户的 sessionid,不带 csrftoken,它会作为 cookie 保存。

我只是在 Pycharm 和 Chrome 的帮助下尝试在 127.0.0.1:63342 上进行调试。

有效设置sn-p:

应用定义

INSTALLED_APPS = (
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.sessions',
    'django.contrib.contenttypes',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'south',

    'tenant',
    'agriculture',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

REST_FRAMEWORK = 
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication',
    ),
    'PAGINATE_BY': 10,
    'PAGINATE_BY_PARAM': 'page_size',


SESSION_COOKIE_DOMAIN = CSRF_COOKIE_DOMAIN = None
SESSION_COOKIE_AGE = 60 * 60 * 24 * 30

# CORS headers settings
CORS_ORIGIN_WHITELIST = (
    'localhost:63342',          # List here all the white-listed access points for the API
    '127.0.0.1:63342',
    ...,
)
CORS_ALLOW_CREDENTIALS = True

相关观点:

class LoginView(APIView):
    """
    The view will respond to the login request by using the underlying Django session authentication. In addition to the
    default behavior will return rich information about the current user being logged in.
    """
    serializer_class = serializers.UserSerializer

    def post(self, request, *args, **kwargs):
        # Get the parameters from the request
        username = request.DATA['username']
        password = request.DATA['password']
        remember = request.DATA.get('remember', False)
        logger.debug('Attempt authentication with %s : "%s"' % (username, password,))
        # Attempt authentication
        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                # Care for the session
                login(request, user)
                # se the expiration to 0 if remember wasn't requested
                if not remember:
                    request.session.set_expiry(0)
                # Return successful response
                logger.debug('Login successfully')
                return Response(self.serializer_class(user).data)
            else:
                logger.warn('User %s is de-activated' % username)
                return Response(status=status.HTTP_403_FORBIDDEN)
        else:
            logger.debug('Unauthorized access with %s : "%s"' % (username, password,))
            return Response(status=status.HTTP_401_UNAUTHORIZED)


class AuthenticateView(APIView):
    """
    Based on the received session token, we will check if the session is still valid, meaning that we will check if the
    user is authenticated. If the request gets to be processed, means that the session token is still valid, otherwise
    we will issue an 401 status. If the session is valid, then return the user data.
    """
    permission_classes = (IsAuthenticated,)
    serializer_class = serializers.UserSerializer

    def get(self, request, *args, **kwargs):
        return Response(self.serializer_class(request.user).data)


class LogoutView(APIView):
    """
    Will simply care to logout the user which was logged in. Will use the default behavior form Django, which doesn't
    require that the uses is logged in.
    """
    def post(self, request, *args, **kwargs):
        logout(request)
        return Response(status=status.HTTP_200_OK)

【问题讨论】:

未设置它们的原因是因为您正在发送Response,而您应该发送RequestContext 的实例,但更重要的是 - 如果外部客户端将使用POST,那么你不应该公开你的 CSRF 令牌(你的端点应该是豁免的)。所以你真的应该为你的 API 禁用 CSRF。 我不同意。该框架对改变服务器状态的方法执行 CSRF,即 POST、PUT、PATCH 和 DESTROY。此外,在 Django Rest Framework 的上下文中,我需要从视图 (rest_framework.response.Response) 发送响应,而不是 RequestContext。 【参考方案1】:

好的,我已经确定了问题。

这与服务器上如何配置 Ajax 调用有关。我只是在成功调用身份验证服务后才尝试设置它们,而这应该在应用程序初始化的第一个场合完成。

因此,后端的代码是正确的并且按预期运行。前端应用程序的更正行为是使用负责 Ajax 初始化的服务组件实现的;我正在使用 Backbone 和 RequireJS 运行它。

ajaxSetup.js 服务:

define([
    'jquery',
    'const'
], function ($, Const) 
    "use strict";

    // these methods don't need csrf header
    var isCsrfSafeMethod = /^(GET|HEAD|OPTIONS|TRACE)$/;

    var setupAjax = function () 
        // Setup the AJAX calls
        $.ajaxSetup(
            // enable authentication
            xhrFields:  withCredentials: true ,

            // setup csrf handling
            beforeSend: function (xhr, settings) 
                if (!isCsrfSafeMethod.test(settings.type) &&
                    $.cookie(Const.CSRF_COOKIE_NAME)) 

                    xhr.setRequestHeader("X-CSRFToken", $.cookie(Const.CSRF_COOKIE_NAME));

                
            ,

            // This will setup an handler for the errors 401, redirecting us to an internal login route.
            statusCode: 
                401: function () 
                    console.log("Unauthorized access, trying to re-direct to login.");
                    // Redirect the to the login page.
                    window.location.replace("#/login");
                
            
        );
    ;

    return  setupAjax: setupAjax ;
);

显然,我有一个模块 Const,它公开了 CSRF_COOKIE_NAME 常量(等等)。当单页应用程序启动时,它会做这样的事情:

desktopInit.js 模块:

define([
    'jquery',
    'backbone',
    'underscore',
    'services/ajaxSetup',
    'services/authentication',
    'text!templates/desktop/body.html',
    'views/desktop/navbar',
    'bootstrap'     // load dependency to be used by views
], function ($, Backbone, _, AjaxSetup, Authentication, BodyTemplate, NavbarView) 

    console.log("Desktop initialization ...");

    // Setup the AJAX calls
    AjaxSetup.setupAjax();

    // Try to authenticate, if there is a logged in user.
    Authentication.authenticate();

    // Fill in the body element with the bare bone layout
    $("body").html(_.template(BodyTemplate, ));

    // we instantiate here the view because we need to control the creation moment of this view, rather then to have it
    // as a singleton served by require.js. At this stage, the bare bone layout for the <body> element exist and the
    // view can find it's anchor and it can render itself properly.
    (new NavbarView()).render();

    // Start the backbone history
    Backbone.history.start();

);

这很有趣......

--罗巴


使用 Angular 的后期编辑:有一个特殊的地方可以放置您的 CSRF 握手代码:在我的一个项目中,在主配置调用中,我正在为 $http 服务执行类似的操作:

(function () 

    angular.module('MyApp').config(['$httpProvider', function () 
        // set up CRSF handshake with Django Rest Framework
        $httpProvider.defaults.xsrfCookieName = CONST.CSRF_COOKIE_NAME;
        $httpProvider.defaults.xsrfHeaderName = CONST.CSRF_HEADER_NAME;
        // we will use credentials
        $httpProvider.defaults.withCredentials = true;
    ]);

)();

【讨论】:

嗨@Roba,我遇到了类似的问题(使用角度而不是主干),我正在发送带有标头的 CSRF 令牌,SessionId cookie 可以吗?任何想法?谢谢 我已经更新了我的答案。 Angular 不像 Backbone 那样冗长。

以上是关于Django-Rest-Framework sessionid 和 csrftoken 未在 Chrome 上设置的主要内容,如果未能解决你的问题,请参考以下文章

断言错误:Django-rest-Framework

记录对 django-rest-framework 的请求

django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器

django-rest-framework 按日期过滤=无

为啥 django-rest-framework 不显示 OneToOneField 数据 - django

如何在 django-rest-framework 中为 API 使用 TokenAuthentication