从iOS向Django服务器发送带有CSRF令牌的POST请求

4

我有一个 iOS 应用程序需要登录到一个使用 Django 的现有网站,并需要 CSRF 令牌才能登录。我无法更改这一点。

我的当前尝试是向服务器发送 GET 请求,该请求将返回 CSRF,然后将该 cookie 作为字符串抓取并附加到 POST 请求中。

NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"http://example.com"]];
[req setHTTPShouldHandleCookies:YES];
[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    self.csrf_cookie = [[(NSHTTPURLResponse *)response allHeaderFields] valueForKey:@"Set-Cookie"];
    [self postLoginCredentialsEmail:@"user@example.com" password:@"password"];
}];

- (void)postLoginCredentialsEmail:(NSString *)email password:(NSString *)password {
    NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"http://example.com/login"]];
    [req addValue:(self.csrf_cookie != nil ? self.csrf_cookie : @"poo") forHTTPHeaderField:@"X-CSRFToken"];
    [req setHTTPMethod:@"POST"];
    NSString *postData = [NSString stringWithFormat:@"password=%@&;email=%@", password, email];
    [req setValue:@"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:@"Content-type"];
    [req setHTTPBody:[postData dataUsingEncoding:NSUTF8StringEncoding]];
    [NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        NSLog(@"response from login: %@", response);
    }];

}

我已获取了cookie,但仍然出现“CSRF令牌不正确或缺失”的错误。
我看过其他帖子,这些代码大多来自那里,但仍然失败。
我已比较服务器在我的请求和网站上的良好请求中收到的内容,唯一的区别似乎是良好请求具有HTTP头字段HTTP_X_CSRFTOKEN,其中只有CSRF令牌,而我的请求具有格式'HTTP_X_CSRFTOKEN': 'csrftoken=tokenkey; expires=expirydate; Max-Age=age; Path=/, mws-track-id=somestuff; httponly; Path=/ '

使用Charles代理验证发送/接收的内容对于网络问题非常重要(提供30天免费试用)。 - zaph
我能够查看客户端和服务器端的所有请求,但仍然没有运气。我已经更新了帖子,并列出了两者之间唯一的区别。 - marisbest2
在Charles中,您可以在消息上设置断点,修改并发送它。您可以尝试一下,看看是否有差异是问题所在。 - zaph
它似乎甚至没有注册我从iOS模拟器发出的请求。我需要单独配置吗? - marisbest2
如何让Charles起作用?(我查看了文档,但找不到任何信息) - marisbest2
3个回答

6

我成功解决了这个问题,方法是使用NSHTTPCookieStorage保存请求中的cookies,然后遍历它们,当cookie.name为@"csrftoken"时,将cookie.value赋值给静态变量csrf_cookie。然后将此变量附加到NSMutableURLRequest的header字段X_CSRFTOKEN上,同时也将完整的NSHTTPCookieStorage附加上去。

这里是一些代码:

// at the top 
static NSString *csrf_cookie;

// in a function:
NSURL *url = [[NSURL alloc] initWithString:@"http://example.com"];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

[request setHTTPShouldHandleCookies:YES];

[request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]]];

// make GET request are store the csrf
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                           NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse *)response allHeaderFields] forURL:url];
                           [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:nil];
                           // for some reason we need to re-store the CSRF token as X_CSRFTOKEN
                           for (NSHTTPCookie *cookie in cookies) {
                               if ([cookie.name isEqualToString:@"csrftoken"]) {
                                   csrf_cookie = cookie.value;
                                   break;
                               }
                           }

// then in later requests:

NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];

[req setHTTPMethod:@"POST"];

[req setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]]];

[req addValue:csrf_cookie forHTTPHeaderField:@"X_CSRFTOKEN"];

1
从相应的API视图中的authentication_classes中去掉SessionAuthentication。这将“禁用”此视图的cookie,这意味着不再需要CSRF令牌。
例如:
authentication_classes = [SessionAuthentication, BasicAuthentication, JSONWebTokenAuthentication]

变成了

authentication_classes = [BasicAuthentication, JSONWebTokenAuthentication]

0
为了完整起见,我会添加我的答案:
您必须创建一个带有标题的请求。
X-CSRF-Token -> Fetch

(根据情况,您可能需要附加某种身份验证)

在您的响应中,您将收到带有令牌的相同标头,例如:

X-CSRF-Token -> <Value>

这是一些SAP企业服务中使用的方法,对于这个Django服务也必须相同。


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