如何使用Django和AngularJS创建一个包括CSRF令牌的POST请求

24

我正在尝试使用angular.js创建一个POST请求到这个Django视图。

class PostJSON4SlickGrid(View):
    """
    REST POST Interface for SlickGrid to update workpackages
    """

    def post(self, request, root_id, wp_id, **kwargs):
        print "in PostJSON4SlickGrid"
        print request.POST
        return HttpResponse(status=200)

因此,我创建了这个资源。

myModule.factory('gridData', function($resource) {
    //define resource class
    var root = {{ root.pk }};
    return $resource('{% url getJSON4SlickGrid root.pk %}:wpID/', {wpID:'@id'},{
            get: {method:'GET', params:{}, isArray:true},
            update:{method:'POST'}
    });
});

在控制器中调用get方法是有效的。URL将被转换为http://127.0.0.1:8000/pm/rest/tree/1/

function gridController($scope, gridData){
    gridData.get(function(result) {
        console.log(result);
        $scope.treeData = result;
        //broadcast that asynchronous xhr call finished
        $scope.$broadcast('mySignal', {fake: 'Hello!'});  
    });
}

我执行更新/POST方法时遇到了问题。

item.$update();

URL被转换为http://127.0.0.1:8000/pm/rest/tree/1/345,缺少尾随斜杠。如果在URL定义中不使用尾随斜杠,则可以轻松地避免此问题。

url(r'^rest/tree/(?P<root_id>\d+)/(?P<wp_id>\d+)$', PostJSON4SlickGrid.as_view(), name='postJSON4SlickGrid'),

取代

url(r'^rest/tree/(?P<root_id>\d+)/(?P<wp_id>\d+)/$', PostJSON4SlickGrid.as_view(), name='postJSON4SlickGrid'),
使用没有斜杠的解决方法后,我现在得到了403(禁止)状态码,这可能是因为我在POST请求中没有传递CSRF令牌。因此,我的问题主要是如何将CSRF令牌传递到由Angular创建的POST请求中?
我知道可以使用此方法通过头文件传递csrf令牌,但我正在寻找一种将令牌添加到post请求正文中的可能性,就像这里所建议的那样。在Angular中是否有可能向post请求正文添加数据?
作为额外的阅读材料,可以查看关于资源、移除尾部斜杠以及当前资源限制的这些讨论: 讨论1讨论2。在其中一个讨论中,其中一位作者建议目前不要使用资源,而是改用方法。
6个回答

40
我知道这已经超过1年了,但如果有人遇到同样的问题,AngularJS已经有一个CSRF cookie获取机制(从1.1.5版本开始),你只需要告诉AngularJS Django使用的cookie名称,以及它应该用于与服务器通信的HTTP标头。
使用模块配置进行设置:
var app = angular.module('yourApp');
app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.xsrfCookieName = 'csrftoken';
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);

现在每个请求都将具有正确的Django CSRF标记。我认为这比手动放置令牌在每个请求上要更正确,因为它使用了两个框架(Django和AngularJS)的内置系统。


你还需要确保使用 ensure_csrf_cookie 装饰器包装你的视图:https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#django.views.decorators.csrf.ensure_csrf_cookie - Eugene Prikazchikov
只有在启用了CSRF中间件后,如果您的视图没有发送CSRF令牌,则会出现这种情况。 - Anoyz
非常感谢,我已经苦思冥想了20个小时。 - Spiderman
如果您的请求不是发送到后端,会发生什么情况?它是否仍会添加 cookie?这不是一个安全问题吗?或者只有当您向$http传递相对 URL 时才会这样做? - Akaisteph7

7

你不能这样打电话吗:

$http({
    method: 'POST',
    url: url,
    data: xsrf,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})

您可以自定义要传递的data,并在其后添加&{{csrf_token}}

在您的资源params:{}中,尝试在params中添加csrfmiddlewaretoken:{{csrf_token}}

编辑:

您可以将数据作为请求正文传递。

item.$update({csrfmiddlewaretoken:{{csrf_token}}})

并且将其作为标题

var csrf = '{{ csrf_token }}'; 
update:{method:'POST', headers: {'X-CSRFToken' : csrf }} 

It is an undocumented issue


谢谢。我更喜欢使用资源功能,因为它通常可以正常工作。我只是缺少添加CSFR令牌的可能性/语法。 - Thomas Kremmel
稍微修改一下,我认为csrf应该在资源调用的参数中。 - Pratik Mandrekar
太好了,我也刚刚发现你可以轻松地设置头部,如下所示:var csrf = '{{ csrf_token }}'; update:{method:'POST', headers: {'X-CSRFToken' : csrf }}。你介意在你的答案中包含这个解决方案以供参考吗? - Thomas Kremmel
请注意:使用标题的方法是有效的。但是,如果您使用item.$update({csrfmiddlewaretoken:{{csrf_token}}}),令牌也会附加到URL中,而不是预期的请求正文中。 - Thomas Kremmel
文档中说:“调用这些方法会使用指定的HTTP方法、目标和参数来调用ng.$http。”因此,如果我真的想要将它传递到正文中,就会使用$http.post('/someUrl', data).success(successCallback);而不是使用$resource。似乎找不到其他解决方案,除了头文件:( - Pratik Mandrekar
显示剩余2条评论

1

最近的angularjs版本中提供的解决方案不起作用。因此,我尝试了以下方法:

  • 首先在标记中添加django标签 {% csrf_token %}。

  • 在您的应用程序配置文件中添加一个 $http 检查器。

angular.module('myApp').config(function ( $httpProvider) {
   $httpProvider.interceptors.push('myHttpRequestInterceptor'); 
});

然后定义我的 myHttpRequestInterceptor。

angular.module("myApp").factory('myHttpRequestInterceptor', function ( ) {

   return {
             config.headers = { 
              'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() } 
             } 
   return config; 
  }}; 
});

它会在所有Angular请求中添加X-CSRFToken

最后,您需要添加Django中间件“django.middleware.csrf.CsrfViewMiddleware”,这将解决CSRF问题。


0
var app = angular.module('angularFoo', ....

app.config(["$httpProvider", function(provider) {
  provider.defaults.headers.common['X-CSRFToken'] = '<<csrftoken value from template or cookie>>';
}])

0
我使用这个:
在 Django 视图中:
@csrf_protect
def index(request):
    #Set cstf-token cookie for rendered template
    return render_to_response('index.html', RequestContext(request))

In App.js:

(function(A) {
    "use strict";
    A.module('DesktopApplication', 'ngCookies' ]).config(function($interpolateProvider, $resourceProvider) {
        //I use {$ and $} as Angular directives
        $interpolateProvider.startSymbol('{$');
        $interpolateProvider.endSymbol('$}');
        //Without this Django not processed urls without trailing slash
        $resourceProvider.defaults.stripTrailingSlashes = false; 
    }).run(function($http, $cookies) {
        //Set csrf-kookie for every request
        $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
        $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    });
}(this.angular));

为了发送正确的请求,您必须将对象转换为参数形式:

$http.post('/items/add/', $.param({name: 'Foo'}));//Here $ is jQuery

0
我已经在Angular 15和Django 4.0中进行了检查,现在可以获取csrf-token。以下是解决方案(已经生效):
  1. 在服务文件中添加函数
authorizeUser(username: string, password: string, csrfmiddlewaretoken: string): Observable<any>{
  const url = 'http://localhost:8000/admin/login/?next=/admin/'
  const encodedBody = new URLSearchParams();
  encodedBody.set('username', username);
  encodedBody.set('password', password);
  encodedBody.set('csrfmiddlewaretoken', csrfmiddlewaretoken)

  return this.http.post(url, encodedBody);

}

添加组件AdminComponent
export class AdminComponent implements OnInit, HttpInterceptor {
  xsrf: string   = <string>this.cookieExtractor.getToken()

  form: FormGroup = new FormGroup({
      username: new FormControl('', Validators.required),
      password: new FormControl('', Validators.required)
    }
  );
  submitted = false

在之前的组件中添加这个重要的功能。
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

  request = request.clone({
    headers:  new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': this.xsrf }),
    withCredentials: true
  });

  return next.handle(request);
}

所以我在编写前端和后端服务的应用程序时遇到的主要问题是content-type必须是application/x-www-form-urlencoded而不是application/json(你可以通过在Django-admin区域发送请求来检查)。希望这个解决方案能对你有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接