使用Django实现用appleid登陆(2)

2,507 阅读4分钟

这是“使用Django使用Apple实施登录功能:食谱”系列的第二部分,旨在为开发人员提供清晰的路径,以便他们在后端使用Django的iOS应用中实现“使用Apple进行登录”功能。

请参考此链接以阅读本系列的第一部分。

2.通过实现使用Apple登录使后端准备就绪

要在Django支持的后端上实现通过Apple登录,我们需要一台运行Django 2.0或更高版本的项目并带有Python Social Auth的服务器。我们已经在本系列的“密钥和标识符的配置” 中拥有了生成的密钥。

API的身份验证部分建立在现有标准OAuth 2.0和OpenID Connect之上。只是为了让您清楚地了解事情的工作原理,整个身份验证流程如下图所示:

建立验证请求

如上图所示,iOS应用程序联系AppleID服务并接收Apple ID凭据,其中包含一个authorizationCode。然后,将该authorizationCode发送到构建身份验证请求的后端。身份验证请求由以下几部分组成:

  • 密钥ID:这是我们在Apple Developer Portal中生成的密钥的ID。
  • 苹果开发团队ID
  • 客户ID:这是iOS应用的捆绑软件ID。例如,com.samplecompany.sampleapp。
  • 客户端密钥

如何创建客户端密钥

客户机密应每次由我们的私钥自己生成。苹果使用ES256 JWT算法生成客户端机密。有关此问题的更多信息,请参见Apple文档,标题为“Creating the Client Secret.”。

在我们的案例中,我们使用了PyJWT来生成客户端密钥。要将PyJWT安装到我们的Django项目中,我们可以在项目文件夹中打开Terminal并输入并运行:

pip install pyjwt

然后,我们可以导入该软件包并按如下所示使用它进行完整的身份验证请求:

import jwt
headers = {
   'kid': settings.SOCIAL_AUTH_APPLE_KEY_ID
}
payload = {
   'iss': settings.SOCIAL_AUTH_APPLE_TEAM_ID,
   'iat': timezone.now(),
   'exp': timezone.now() + timedelta(days=180),
   'aud': 'https://appleid.apple.com',
   'sub': settings.CLIENT_ID,
}
client_secret = jwt.encode(
   payload, 
   settings.SOCIAL_AUTH_APPLE_PRIVATE_KEY, 
   algorithm='ES256', 
   headers=headers
).decode("utf-8")

有关有效负载中提供的密钥的更多信息,请参见Apple文档

实现自己的后端代码

由于我们已经安排了构建身份验证请求的时间,因此我们现在可以开始在后端工作。尽管Python Social Auth实现了OAuth 2.0标准,但是我们必须扩展BaseOAuth2并覆盖某些功能,因为Apple的流程有所不同。我们将不得不重写以下方法:

  • get_user_details:需要重写此方法,以将电子邮件地址或其他用户信息返回给Python Social Auth框架。
  • get_key_and_secret:我们需要重写此方法,因为我们必须以上述方式生成客户端密钥。
  • do_auth:因为我们需要验证移动客户端从Apple发送的访问令牌,并获取可以从中提取其他详细信息的ID令牌,所以我们需要重写此方法。

为什么ID token如此重要

苹果的文档中也对此进行了说明。 现在,让我们创建一个名为AppleOAuth2的类作为自定义后端,该类处理使用Python Social Auth与Apple进行登录。

import jwt
import requests
from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from social_core.backends.oauth import BaseOAuth2
from social_core.utils import handle_http_errors


class AppleOAuth2(BaseOAuth2):
    """apple authentication backend"""

    name = 'apple'
    ACCESS_TOKEN_URL = 'https://appleid.apple.com/auth/token'
    SCOPE_SEPARATOR = ','
    ID_KEY = 'uid'

    @handle_http_errors
    def do_auth(self, access_token, *args, **kwargs):
        """
        Finish the auth process once the access_token was retrieved
        Get the email from ID token received from apple
        """
        response_data = {}
        client_id, client_secret = self.get_key_and_secret()

        headers = {'content-type': "application/x-www-form-urlencoded"}
        data = {
            'client_id': client_id,
            'client_secret': client_secret,
            'code': access_token,
            'grant_type': 'authorization_code',
        }

        res = requests.post(AppleOAuth2.ACCESS_TOKEN_URL, data=data, headers=headers)
        response_dict = res.json()
        id_token = response_dict.get('id_token', None)

        if id_token:
            decoded = jwt.decode(id_token, '', verify=False)
            response_data.update({'email': decoded['email']}) if 'email' in decoded else None
            response_data.update({'uid': decoded['sub']}) if 'sub' in decoded else None

        response = kwargs.get('response') or {}
        response.update(response_data)
        response.update({'access_token': access_token}) if 'access_token' not in response else None

        kwargs.update({'response': response, 'backend': self})
        return self.strategy.authenticate(*args, **kwargs)

    def get_user_details(self, response):
        email = response.get('email', None)
        details = {
            'email': email,
        }
        return details

    def get_key_and_secret(self):
        headers = {
            'kid': settings.SOCIAL_AUTH_APPLE_KEY_ID
        }

        payload = {
            'iss': settings.SOCIAL_AUTH_APPLE_TEAM_ID,
            'iat': timezone.now(),
            'exp': timezone.now() + timedelta(days=180),
            'aud': 'https://appleid.apple.com',
            'sub': settings.CLIENT_ID,
        }

        client_secret = jwt.encode(
            payload, 
            settings.SOCIAL_AUTH_APPLE_PRIVATE_KEY, 
            algorithm='ES256', 
            headers=headers
        ).decode("utf-8")
        
        return settings.CLIENT_ID, client_secret

我们要记住的一件事是,只有在我们第一次发出请求时,电子邮件地址和名称才会返回。因此,如果出现需要反复测试的情况,则需要每次从AppleID帐户的授权应用程序列表中删除我们的应用程序。

如果我不想使用Python Social Auth怎么办?

如果您不想使用Python Social Auth,则可以在用户验证后手动创建用户,并解码我们从Apple收到的id_token字段的值。如果uid已经存在,那么我们必须了解它是同一用户。因此,我们只需要登录即可。但是,如果您使用Python Social Auth,所有这些事情都会自动处理。

现在,我们已经设置了后端,使其可用于使用Apple登录功能。剩下的唯一步骤是第三步,也是最后一步-即在我们的iOS应用中实现“使用Apple登录”。如果您还没有阅读第一步,请查看本系列的第一部分