使用iOS设备的Facebook令牌登录django-allauth

11

我正在使用Facebook iOS SDK将Facebook访问令牌POST到我的Django服务器URI。相应的views.py函数如下所示,当我从iOS进行POST时,我会得到200个响应代码。但是,在此之后,我立即从iOS设备调用第二个@login_required修饰的URI,它认为我未登录并重定向到主页。我做错了什么?在iOS成功POST后,如何保持已登录状态?

# For POSTing the facebook token
from django.views.decorators.csrf import csrf_exempt
from allauth.socialaccount import providers
from allauth.socialaccount.models import SocialLogin, SocialToken, SocialApp
from allauth.socialaccount.providers.facebook.views import fb_complete_login
from allauth.socialaccount.helpers import complete_social_login

# Log in from Facebook
@csrf_exempt
def mobile_facebook_login(request):
    response = HttpResponse() ## Create an HTTP Response Object
    if request.method == "POST": # The method better be a POST
        access_token = request.POST.get('access_token') # Get token   
        try:
            app = SocialApp.objects.get(provider="facebook")
            token = SocialToken(app=app, token=access_token)

            # Check token against facebook                  
            login = fb_complete_login(request, app, token)
            login.token = token
            login.state = SocialLogin.state_from_request(request)

            # Add or update the user into users table
            ret = complete_social_login(request, login)

            # If we get here we've succeeded
            response['Auth-Response'] = 'success'
            response.status_code = 200 # Set status  
            return response   
        except Exception,e:
            # If we get here we've failed
            response['Auth-Response'] = 'failure: %s'%(e)
            response.status_code = 401 # Set status
            return response
    else:
        # If we get here we've failed
        response['Auth-Response'] = 'failure'
        response.status_code = 401 # Set status
        return response

======= 更新 ==========

好的,谢谢您的评论。所以现在我也将Facebook电子邮件地址作为POST并手动获取用户并登录。然而,后续请求仍未经过身份验证。因此,@login_required装饰器仍然失败了...还有其他想法吗?

# Log in from Facebook
@csrf_exempt
def mobile_facebook_login(request):
    response = HttpResponse() ## Create an HTTP Response Object
    if request.method == "POST": # The method better be a POST
        access_token = request.POST.get('access_token') # Get token
        email = request.POST.get('email') # Get email
        try:
            app = SocialApp.objects.get(provider="facebook")
            token = SocialToken(app=app, token=access_token)

            # Check token against facebook                  
            login = fb_complete_login(request, app, token)
            login.token = token
            login.state = SocialLogin.state_from_request(request)

            # Add or update the user into users table
            ret = complete_social_login(request, login)

            # Try to get username from email
            try:
                        user = User.objects.get(email=email) # Get User
                # Login the user from Django's perspective
                user.backend = 'django.contrib.auth.backends.ModelBackend'
                auth_login(request,user)
                except User.DoesNotExist:
                        # If we get here we've failed
                response['Auth-Response'] = 'failure: %s'%(e)
                response.status_code = 401 # Set status
                return response

            # If we get here we've succeeded
            response['Auth-Response'] = 'success'
            response.status_code = 200 # Set status  
            return response   
        except Exception,e:
            # If we get here we've failed
            response['Auth-Response'] = 'failure: %s'%(e)
            response.status_code = 401 # Set status
            return response
    else:
        # If we get here we've failed
        response['Auth-Response'] = 'failure'
        response.status_code = 401 # Set status
        return response

==== 另一个更新 ==========

根据这篇文章中的第二个答案: django authentication without a password

我创建了一个自定义的登录后端,不需要密码。该文章中的第三个答案讨论了如何实现这一点:

user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)

不在会话中存储登录验证信息,因此我尝试使用自定义后端。

这是我修改过的代码:

# Log in from Facebook
@csrf_exempt
def mobile_facebook_login(request):
    response = HttpResponse() ## Create an HTTP Response Object
    if request.method == "POST": # The method better be a POST
        access_token = request.POST.get('access_token') # Get token
        email = request.POST.get('email') # Get email
        try:
            app = SocialApp.objects.get(provider="facebook")
            token = SocialToken(app=app, token=access_token)

            # Check token against facebook                  
            login = fb_complete_login(request, app, token)
            login.token = token
            login.state = SocialLogin.state_from_request(request)

            # Add or update the user into users table
            ret = complete_social_login(request, login)

            # Try to get username from email
            try:
                        user = User.objects.get(email=email) # Get User
                # Login the user from Django's perspective
                user.backend = 'django_tours.auth_backend.PasswordlessAuthBackend'
                user = authenticate(email=user.email)
                auth_login(request,user)
                #request.session.cycle_key()
                    except User.DoesNotExist:
                        # If we get here we've failed
                response['Auth-Response'] = 'failure: %s'%(e)
                response.status_code = 401 # Set status
                return response

            # If we get here we've succeeded
            response['Auth-Response'] = 'success'
            response['User-Is-Authenticated'] = '%s'%(request.user.is_authenticated())
            response.status_code = 200 # Set status  
            return response   
        except Exception,e:
            # If we get here we've failed
            response['Auth-Response'] = 'failure: %s'%(e)
            response.status_code = 401 # Set status
            return response
    else:
        # If we get here we've failed
        response['Auth-Response'] = 'failure'
        response.status_code = 401 # Set status
        return response

我使用hurl.it得到了HTTP 200响应,但仍然无法在iPhone上登录:

Auth-Response: success
Content-Encoding: gzip
Content-Length: 20
Content-Type: text/html; charset=utf-8
Date: Thu, 08 May 2014 00:22:47 GMT
Server: Apache/2.2.22 (Ubuntu)
Set-Cookie: csrftoken=UuJDP6OB3YCSDtXLEa10MgJ70tDtIfZX; expires=Thu, 07-May-2015 00:22:48 GMT; Max-Age=31449600; Path=/, sessionid=kdr061v1pcsbqtvgsn3pyyqj9237z6k8; expires=Thu, 22-May-2014 00:22:48 GMT; httponly; Max-Age=1209600; Path=/, messages="4f919699a4730a3df220a0eb3799ed59d2756825$[[\"__json_message\"\0540\05425\054\"Successfully signed in as philbot.\"]]"; Path=/
User-Is-Authenticated: True
Vary: Cookie,Accept-Encoding

你尝试在浏览器中操作了吗?如果从浏览器调用URL,问题是否再次出现?iOS SDK中启用了Cookie吗? - Eugene Soldatov
我认为你不能使用@login_required。下一次调用Django视图时,Django不知道用户是谁,因为SDK不会传递cookie信息。一个解决方案是在每次调用视图时传递令牌,并使用自定义装饰器登录用户。请记住,这不是最好的前进方式。 - Kiran Ruth R
我对allauth没有太多的信息。然而,我知道Django的login_required会执行类似于if request.user.is_authorized()==True的操作。除非是allauth在为您执行此操作,否则我没有看到任何更改用户授权状态的内容。因此,我的建议是更改您的django.auth用户的状态,并使其is_authorized = True。确保创建了Session,以便当用户移动到另一个视图时,Django可以检查用户的身份验证状态。让我们看看在此过程之后发生了什么,这样我们就可以帮助您。 - Othman
请发布JavaScript代码。如何“紧接着”立即发送下一个请求? - jordiburgos
没有 JavaScript 代码。下一个请求是从 iOS 上的 UIViewController 发送的,因此一旦上面的函数返回 HTTP 200 成功状态,那么在 iOS 应用程序中立即呈现一个新的视图控制器,并使用 NSURLConnection 立即从 iOS 启动 HTTP Get。问题是 Django 不认为 iOS 设备已登录。我不确定为什么令牌没有被正确使用。 - PhilBot
2个回答

1
我遇到了与您非常相似的问题,在iOS应用程序中实现从Facebook登录到运行django-allauth的服务器时。我注意到在iOS的成功POST响应中,sessionid cookie没有被自动保存,这可能是您的后续调用被拒绝并重定向到主页面的原因。
添加以下行似乎对我有用,但我承认我不完全理解它为什么有效。也许与刷新会话密钥有关。无论如何,由于没有其他答案,我认为这可能对您有所帮助:
user = User.objects.get(email=email) # Get User
# Login the user from Django's perspective
user.backend = 'django.contrib.auth.backends.ModelBackend'
auth_login(request,user)
request.session.cycle_key() #Refresh session key

然后,在iOS应用程序方面,我检查是否存在会话cookie:
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:WEB_APP_BASE_URL]];
for (NSHTTPCookie *cookie in cookies)
{
    if ([cookie.name isEqualToString:@"sessionid"]) {
        NSLog(@"found session cookie: %@",cookie.value);
    }
}

谢谢您的建议,我已经进行了更改(cycle_key()),但是在Django 1.6中我仍然没有被视为“已登录”。还有其他想法吗? - PhilBot
在我的iOS应用程序中,我会检查服务器是否已经发送回会话cookie(然后您的应用程序会自动在随后的请求中使用它)。您可以尝试这样做来帮助您进行调试。 - weiy

0
感谢所有的帮助和建议——我终于解决了问题。我不知道使用Facebook登录会导致cookie混乱,而标准登录却可以正常工作的确切原因。但我注意到从Facebook登录返回的cookie域名前面有一个"."。
[ .domain.com ]

标准登录的 cookie 域名如下:

[ www.domain.com ]

我成功使用Facebook登录后,从HTTP响应中解析了cookies并将它们存储在单例中:

                // Extract cookie information
                NSRange range = [cookieString rangeOfString:@"csrftoken="];
                if (range.location!=NSNotFound){
                    cookieString = [cookieString substringFromIndex:NSMaxRange(range)];
                    range = [cookieString rangeOfString:@";"];
                    if (range.location!=NSNotFound){
                        self.appDelegate.djangoCsrftoken = [cookieString substringToIndex:range.location];
                    }
                }
                range = [cookieString rangeOfString:@"sessionid="];
                if (range.location!=NSNotFound){
                    cookieString = [cookieString substringFromIndex:NSMaxRange(range)];
                    range = [cookieString rangeOfString:@";"];
                    if (range.location!=NSNotFound){
                        self.appDelegate.djangoSessionId = [cookieString substringToIndex:range.location];
                    }
                }

                if (LOGIN_DEBUG) { // Debug the response
                    NSLog(@"Extracted csrftoken is: %@",self.appDelegate.djangoCsrftoken);
                    NSLog(@"Extracted sessionid is: %@",self.appDelegate.djangoSessionId);
                }

因此,我专门为以下请求创建了这些cookie:

    // Clear all cookies when app launches
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *each in cookieStorage.cookies) {
        //if ( [each.domain isEqualToString:DOMAIN] ) {
        NSLog(@"Deleting cookie: %@ -- %@",each.name,each.domain);
        [cookieStorage deleteCookie:each];
        //}
    }

    //////////////// CSRF TOKEN /////////////////////

    // Create cookies based on parsed values
    NSMutableDictionary *cookieCsrfProperties = [NSMutableDictionary dictionary];
    [cookieCsrfProperties setObject:@"csrftoken" forKey:NSHTTPCookieName];
    [cookieCsrfProperties setObject:self.appDelegate.djangoCsrftoken forKey:NSHTTPCookieValue];
    [cookieCsrfProperties setObject:DOMAIN forKey:NSHTTPCookieDomain];
    [cookieCsrfProperties setObject:DOMAIN forKey:NSHTTPCookieOriginURL];
    [cookieCsrfProperties setObject:@"/" forKey:NSHTTPCookiePath];
    [cookieCsrfProperties setObject:@"0" forKey:NSHTTPCookieVersion];

    // Set expiration to one month from now or any NSDate of your choosing
    // this makes the cookie sessionless and it will persist across web sessions and app launches
    /// if you want the cookie to be destroyed when your app exits, don't set this
    [cookieCsrfProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];

    NSHTTPCookie *csrfCookie = [NSHTTPCookie cookieWithProperties:cookieCsrfProperties];
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:csrfCookie];

    //////////////// SessionId TOKEN /////////////////////

    // Create cookies based on parsed values
    NSMutableDictionary *cookieSessionIdProperties = [NSMutableDictionary dictionary];
    [cookieSessionIdProperties setObject:@"sessionid" forKey:NSHTTPCookieName];
    [cookieSessionIdProperties setObject:self.appDelegate.djangoSessionId forKey:NSHTTPCookieValue];
    [cookieSessionIdProperties setObject:DOMAIN forKey:NSHTTPCookieDomain];
    [cookieSessionIdProperties setObject:DOMAIN forKey:NSHTTPCookieOriginURL];
    [cookieSessionIdProperties setObject:@"/" forKey:NSHTTPCookiePath];
    [cookieSessionIdProperties setObject:@"0" forKey:NSHTTPCookieVersion];

    // Set expiration to one month from now or any NSDate of your choosing
    // this makes the cookie sessionless and it will persist across web sessions and app launches
    /// if you want the cookie to be destroyed when your app exits, don't set this
    [cookieCsrfProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];

    NSHTTPCookie *sessionIdCookie = [NSHTTPCookie cookieWithProperties:cookieSessionIdProperties];
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:sessionIdCookie];

    ///////////////////////////////////////////////////

    // Create request
    NSURL *url = [NSURL URLWithString:requestUrl];
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    urlRequest.HTTPShouldHandleCookies = YES;

    NSHTTPCookie *setCookie;
    for (setCookie in [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies) {
        if ( ([setCookie.name isEqualToString:@"csrftoken" ] || [setCookie.name isEqualToString:@"sessionid"]) ) {
            NSLog(@"Adding Cookie: %@ = %@  [ %@ ]", setCookie.name, setCookie.value, setCookie.domain);
            [urlRequest addValue:setCookie.value forHTTPHeaderField:setCookie.name];
        }
    }
    NSURLResponse *response = nil;
    NSError * error = nil;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];

在这之后,我成功地使用Django-allauth登录了Facebook。


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