22

有人可以解释使用其他 OAuth2 提供程序实现登录过程的步骤吗?此链接Google Cloud Endpoints 与另一个 oAuth2 提供程序几乎没有提供有关编写自定义身份验证的信息,但我想对于像我这样的初学者来说这还不够,请提供详细步骤。特别是对 Facebook 感兴趣。

4

5 回答 5

20

您需要根据 Facebook 的文档和您将客户端应用程序部署到的环境(浏览器、iOS 和 Android)来实现 Facebook 的客户端 API。这包括向他们注册您的应用程序。您注册的应用程序将指导用户通过身份验证流程,最后您的客户端应用程序将有权访问短期访问令牌。Facebook 有多种类型的访问令牌,但听起来您感兴趣的一种称为用户访问令牌,因为它识别授权用户。

通过字段或标头将访问令牌传递给您的 Cloud Endpoints API。在您的 API 代码内部接收访问令牌并实现 Facebook 的 API,以检查访问令牌的有效性。这个 SO question的第一个答案使它看起来相当容易,但您可能想再次参考他们的文档。如果该检查通过,那么您将运行您的 API 代码,否则抛出异常。

您通常还需要实现缓存机制,以防止为每个 Cloud Endpoints 请求调用 Facebook 服务器端验证 API。

最后,我提到您的客户端应用程序有一个短暂的令牌。如果您有一个基于浏览器的客户端应用程序,那么您可能希望将其升级为长期存在的令牌。Facebook 也有一个流程,其中涉及您的 API 代码请求一个长期存在的令牌和一个短期的令牌。然后,您需要将该长期存在的令牌传输回客户端应用,以用于未来的 Cloud Endpoints API 调用。

如果您的客户端应用程序是基于 iOS 或 Android 的,那么您的令牌由 Facebook 代码管理,您只需在需要时从相应的 API 请求访问令牌。

于 2013-09-10T20:47:59.853 回答
5

所以我实际上尝试实现该自定义身份验证流程。尽管在安全方面可能会进一步考虑,但它似乎工作正常。

首先,用户转到我的应用程序并使用 facebook 进行身份验证,应用程序得到了他的 user_id 和 access_token。然后应用程序使用这些信息向服务器调用 auth API。

class AuthAPI(remote.Service):
    @classmethod
    def validate_facebook_user(cls, user_id, user_token):
        try:
            graph = facebook.GraphAPI(user_token)
            profile = graph.get_object("me", fields='email, first_name, last_name, username')
        except facebook.GraphAPIError, e:
            return (None, None, str(e))

        if (profile is not None):
            # Check if match user_id
            if (profile.get('id', '') == user_id):
                # Check if user exists in our own datastore
                (user, token) = User.get_by_facebook_id(user_id, 'auth', user_token)
                # Create new user if not 
                if user is None:
                    #print 'Create new user'
                    username = profile.get('username', '')
                    password = security.generate_random_string(length=20)
                    unique_properties = ['email_address']
                    if (username != ''):
                        (is_created, user) = User.create_user(
                            username,
                            unique_properties,
                            email_address = profile.get('email', ''),
                            name = profile.get('first_name', ''),
                            last_name = profile.get('last_name', ''),
                            password_raw = password,
                            facebook_id = user_id,
                            facebook_token = user_token,
                            verified=False,
                        )
                        if is_created==False:
                            return (None, None, 'Cannot create user')
                        token_str = User.create_auth_token(user.get_id())
                        #print (user, token_str)
                # Return if user exists 
                if token is not None:
                    return (user, token.token, 'Successfully logged in')
                else:
                    return (None, None, 'Invalid token')
        return (None, None, 'Invalid facebook id and token')
    # Return a user_id and token if authenticated successfully
    LOGIN_REQ = endpoints.ResourceContainer(MessageCommon, 
                                           type=messages.StringField(2, required=True),
                                           user_id=messages.StringField(3, required=False),
                                           token=messages.StringField(4, required=False))    

    @endpoints.method(LOGIN_REQ, MessageCommon,
                  path='login', http_method='POST', name='login')
    def login(self, request):
        type = request.type
        result = MessageCommon()
        # TODO: Change to enum type if we have multiple auth ways
        if (type == "facebook"):
            # Facebook user validation
            user_id = request.user_id
            access_token = request.token
            (user_obj, auth_token, msg) = self.validate_facebook_user(user_id, access_token)
            # If we can get user data
            if (user_obj is not None and auth_token is not None):
                print (user_obj, auth_token)
                result.success = True
                result.message = msg
                result.data = json.dumps({
                    'user_id': user_obj.get_id(),
                    'user_token': auth_token
                })
            # If we cannot
            else:
                result.success = False
                result.message = msg
        return result

除此之外,您可能希望按照此处的说明实施正常的用户身份验证流程:http: //blog.abahgat.com/2013/01/07/user-authentication-with-webapp2-on-google-app-engine /

这是因为我获取的 user_id 和 user_token 是由webapp2_extras.appengine.auth提供的。

User.get_by_facebook_id 的实现:

class User(webapp2_extras.appengine.auth.models.User):
    @classmethod
    def get_by_facebook_id(cls, fb_id, subj='auth', fb_token=""):
        u = cls.query(cls.facebook_id==fb_id).get()
        if u is not None:
            user_id = u.key.id()
            # TODO: something better here, now just append the facebook_token to a prefix
            token_str = "fbtk" + str(fb_token) 
            # get this token if it exists 
            token_key = cls.token_model.get(user_id, subj, token_str)
            print token_key, fb_token
            if token_key is None:
                # return a token that created from access_token string
                if (fb_token == ""):
                    return (None, None)
                else:
                    token = cls.token_model.create(user_id, subj, token_str)
            else: 
                token = token_key
            return (u, token)
        return (None, None)

服务器验证用户是否再次通过 facebook 验证。如果通过,则认为用户已登录。在这种情况下,服务器从我们的数据存储区传回 user_token(基于 facebook_token 生成)和 user_id。

任何进一步的 API 调用都应该使用这个 user_id 和 user_token

def get_request_class(messageCls):
    return endpoints.ResourceContainer(messageCls, 
                               user_id=messages.IntegerField(2, required=False),
                               user_token=messages.StringField(3, required=False)) 
def authenticated_required(endpoint_method):
    """
    Decorator that check if API calls are authenticated
    """
    def check_login(self, request, *args, **kwargs):
        try:
            user_id = request.user_id
            user_token = request.user_token
            if (user_id is not None and user_token is not None):
                # Validate user 
                (user, timestamp) = User.get_by_auth_token(user_id, user_token)
                if user is not None:
                    return endpoint_method(self, request, user, *args, **kwargs )
            raise endpoints.UnauthorizedException('Invalid user_id or access_token')
        except:
            raise endpoints.UnauthorizedException('Invalid access token')


@endpoints.api(name='blah', version='v1', allowed_client_ids = env.CLIENT_IDS, auth=AUTH_CONFIG)
class BlahApi(remote.Service):

    # Add user_id/user_token to the request 
    Blah_Req = get_request_class(message_types.VoidMessage)
    @endpoints.method(Blah_Req, BlahMessage, path='list', name='list')
    @authenticated_required
    def blah_list(self, request, user):
        newMessage = BlahMessage(Blah.query().get())
        return newMessage

笔记:

于 2014-03-19T03:52:19.543 回答
4

我通过添加一个 webapp2 处理程序来实现这个用例,将 Facebook 访问令牌交换为我自己的应用程序生成的令牌,使用SimpleAuth mixin 进行验证:

class AuthHandler(webapp2.RequestHandler, SimpleAuthHandler):
    """Authenticates a user to the application via a third-party provider.

    The return value of this request is an OAuth token response.

    Only a subset of the PROVIDERS specified in SimpleAuthHandler are currently supported.
    Tested providers: Facebook
    """
    def _on_signin(self, data, auth_info, provider):
        # Create the auth ID format used by the User model
        auth_id = '%s:%s' % (provider, data['id'])
        user_model = auth.get_auth().store.user_model
        user = user_model.get_by_auth_id(auth_id)

        if not user:
            ok, user = user_model.create_user(auth_id)
            if not ok:
                logging.error('Unable to create user for auth_id %s' % auth_id)
                self.abort(500, 'Unable to create user')

        return user

    def post(self):
        # Consider adding a check for a valid endpoints client ID here as well.

        access_token = self.request.get('x_access_token')
        provider = self.request.get('x_provider')

        if provider not in self.PROVIDERS or access_token is None:
            self.abort(401, 'Unknown provider or access token')

        auth_info = {'access_token': access_token}
        fetch_user_info = getattr(self, '_get_%s_user_info' % provider)
        user_info = fetch_user_info(auth_info)

        if 'id' in user_info:
            user = self._on_signin(user_info, auth_info, provider)
            token = user.create_bearer_token(user.get_id())

            self.response.content_type = 'application/json'
            self.response.body = json.dumps({
                'access_token': token.token,
                'token_type': 'Bearer',
                'expires_in': token.bearer_token_timedelta.total_seconds(),
                'refresh_token': token.refresh_token
            })
        else:
            self.abort(401, 'Access token is invalid')

交换的访问令牌可以在 Authorization 标头中的每个端点请求上传递,或者如果您愿意,也可以作为 RPC 消息的一部分。这是从标题中读取它的示例:

def get_current_user():
    token = os.getenv('HTTP_AUTHORIZATION')
    if token:
        try:
            token = token.split(' ')[1]
        except IndexError:
            pass

    user, _ = User.get_by_bearer_token(token)
    return user

我在 Github 上发布了完整的示例:https ://github.com/loudnate/appengine-endpoints-auth-example

于 2014-04-20T03:05:52.173 回答
1

因此,没有人对 android 客户端的东西有所了解。由于在这种情况下您不需要 Google 登录,因此获取 api 句柄的代码如下所示:

private Api getEndpointsApiHandle() {
    Api.Builder api = new Api.Builder(HTTP_TRANSPORT, JSON_FACTORY, null);
    api.setRootUrl(yourRootUrl);
    return api.build();
}

如果您注意到;您将需要将 null 作为凭据传递。这段代码就像一个魅力

于 2015-09-29T14:00:27.380 回答
0

我也为这个问题编写了自己的解决方案。你可以在这里查看代码:https ://github.com/rggibson/Authtopus

Authtopus 是一个 Python 库,用于使用 Google Cloud Endpoints 进行自定义身份验证。它支持基本的用户名和密码注册 + 登录,以及通过 Facebook 和 Google 登录(并且可以扩展以支持其他社交提供商而无需太多麻烦)。我知道这并不能直接回答原始问题,但它似乎足够相关,我想我会分享。

于 2015-11-15T04:32:27.967 回答