59 votes

D'Authentification personnalisé pour Google Cloud Endpoints (au lieu de OAuth2)

Nous sommes super excités à propos de App Engine support pour Google Cloud Endpoints.

Cela dit, nous n'utilisons pas OAuth2 encore et généralement authentifier les utilisateurs avec nom d'utilisateur/mot de passe nous pouvons donc prendre en charge les clients qui n'ont pas de comptes Google.

Nous voulons migrer notre API sur Google Cloud Endpoints parce que de tous les avantages que l'on obtient gratuitement (API Console, les Bibliothèques Client, robustesse, ...) mais notre question principale est de ...

Comment ajouter une authentification personnalisée à nuage de points de terminaison, où nous avons déjà vérifier la validité d'une session d'utilisateur + jeton CSRF dans notre API existantes.

Est-il une manière élégante de le faire sans ajouter des trucs comme les informations de session et les jetons CSRF de la protoRPC messages?

16voto

Doug Correa Points 353

Je suis en utilisant webapp2 système d'Authentification pour l'ensemble de ma demande. J'ai donc essayé de le réutiliser pour Google d'Authentification dans le Cloud et je le reçois!

webapp2_extras.auth utilise webapp2_extras.sessions pour stocker auth de l'information. Et il cette séance pourrait être stockés dans 3 formats différents: securecookie, magasin de données ou memcache.

Securecookie est le format par défaut, et dont je suis l'aide. Je considère qu'il est assez sûr que webapp2 auth système est utilisé pour beaucoup de GAE application en cours d'exécution dans la production de l'environnement.

J'ai donc décoder ce securecookie et la réutilisation de GAE points de terminaison. Je ne sais pas si cela pourrait générer quelques sécurisé problème (j'espère pas) mais peut-être @bossylobster pourrait dire si c'est ok à la recherche à la sécurité.

Mon Api:

import Cookie
import logging
import endpoints
import os
from google.appengine.ext import ndb
from protorpc import remote
import time
from webapp2_extras.sessions import SessionDict
from web.frankcrm_api_messages import IdContactMsg, FullContactMsg, ContactList, SimpleResponseMsg
from web.models import Contact, User
from webapp2_extras import sessions, securecookie, auth
import config

__author__ = 'Douglas S. Correa'

TOKEN_CONFIG = {
    'token_max_age': 86400 * 7 * 3,
    'token_new_age': 86400,
    'token_cache_age': 3600,
}

SESSION_ATTRIBUTES = ['user_id', 'remember',
                      'token', 'token_ts', 'cache_ts']

SESSION_SECRET_KEY = '9C3155EFEEB9D9A66A22EDC16AEDA'


@endpoints.api(name='frank', version='v1',
               description='FrankCRM API')
class FrankApi(remote.Service):
    user = None
    token = None

    @classmethod
    def get_user_from_cookie(cls):
        serializer = securecookie.SecureCookieSerializer(SESSION_SECRET_KEY)
        cookie_string = os.environ.get('HTTP_COOKIE')
        cookie = Cookie.SimpleCookie()
        cookie.load(cookie_string)
        session = cookie['session'].value
        session_name = cookie['session_name'].value
        session_name_data = serializer.deserialize('session_name', session_name)
        session_dict = SessionDict(cls, data=session_name_data, new=False)

        if session_dict:
            session_final = dict(zip(SESSION_ATTRIBUTES, session_dict.get('_user')))
            _user, _token = cls.validate_token(session_final.get('user_id'), session_final.get('token'),
                                               token_ts=session_final.get('token_ts'))
            cls.user = _user
            cls.token = _token

    @classmethod
    def user_to_dict(cls, user):
        """Returns a dictionary based on a user object.

        Extra attributes to be retrieved must be set in this module's
        configuration.

        :param user:
            User object: an instance the custom user model.
        :returns:
            A dictionary with user data.
        """
        if not user:
            return None

        user_dict = dict((a, getattr(user, a)) for a in [])
        user_dict['user_id'] = user.get_id()
        return user_dict

    @classmethod
    def get_user_by_auth_token(cls, user_id, token):
        """Returns a user dict based on user_id and auth token.

        :param user_id:
            User id.
        :param token:
            Authentication token.
        :returns:
            A tuple ``(user_dict, token_timestamp)``. Both values can be None.
            The token timestamp will be None if the user is invalid or it
            is valid but the token requires renewal.
        """
        user, ts = User.get_by_auth_token(user_id, token)
        return cls.user_to_dict(user), ts

    @classmethod
    def validate_token(cls, user_id, token, token_ts=None):
        """Validates a token.

        Tokens are random strings used to authenticate temporarily. They are
        used to validate sessions or service requests.

        :param user_id:
            User id.
        :param token:
            Token to be checked.
        :param token_ts:
            Optional token timestamp used to pre-validate the token age.
        :returns:
            A tuple ``(user_dict, token)``.
        """
        now = int(time.time())
        delete = token_ts and ((now - token_ts) > TOKEN_CONFIG['token_max_age'])
        create = False

        if not delete:
            # Try to fetch the user.
            user, ts = cls.get_user_by_auth_token(user_id, token)
            if user:
                # Now validate the real timestamp.
                delete = (now - ts) > TOKEN_CONFIG['token_max_age']
                create = (now - ts) > TOKEN_CONFIG['token_new_age']

        if delete or create or not user:
            if delete or create:
                # Delete token from db.
                User.delete_auth_token(user_id, token)

                if delete:
                    user = None

            token = None

        return user, token

    @endpoints.method(IdContactMsg, ContactList,
                      path='contact/list', http_method='GET',
                      name='contact.list')
    def list_contacts(self, request):

        self.get_user_from_cookie()

        if not self.user:
            raise endpoints.UnauthorizedException('Invalid token.')

        model_list = Contact.query().fetch(20)
        contact_list = []
        for contact in model_list:
            contact_list.append(contact.to_full_contact_message())

        return ContactList(contact_list=contact_list)

    @endpoints.method(FullContactMsg, IdContactMsg,
                      path='contact/add', http_method='POST',
                      name='contact.add')
    def add_contact(self, request):
        self.get_user_from_cookie()

        if not self.user:
           raise endpoints.UnauthorizedException('Invalid token.')


        new_contact = Contact.put_from_message(request)

        logging.info(new_contact.key.id())

        return IdContactMsg(id=new_contact.key.id())

    @endpoints.method(FullContactMsg, IdContactMsg,
                      path='contact/update', http_method='POST',
                      name='contact.update')
    def update_contact(self, request):
        self.get_user_from_cookie()

        if not self.user:
           raise endpoints.UnauthorizedException('Invalid token.')


        new_contact = Contact.put_from_message(request)

        logging.info(new_contact.key.id())

        return IdContactMsg(id=new_contact.key.id())

    @endpoints.method(IdContactMsg, SimpleResponseMsg,
                      path='contact/delete', http_method='POST',
                      name='contact.delete')
    def delete_contact(self, request):
        self.get_user_from_cookie()

        if not self.user:
           raise endpoints.UnauthorizedException('Invalid token.')


        if request.id:
            contact_to_delete_key = ndb.Key(Contact, request.id)
            if contact_to_delete_key.get():
                contact_to_delete_key.delete()
                return SimpleResponseMsg(success=True)

        return SimpleResponseMsg(success=False)


APPLICATION = endpoints.api_server([FrankApi],
                                   restricted=False)

1voto

siebz0r Points 3960

De ma compréhension Google Cloud Endpoints offre un moyen de mettre en œuvre une (Reposant?) API et de générer un client mobile de la bibliothèque. L'authentification dans ce cas serait OAuth2. OAuth2 fournit différents "flux", parmi lesquels le soutien des clients mobiles. Dans le cas de l'authentification à l'aide de l'un des principaux et des informations d'identification (nom d'utilisateur et mot de passe), cela ne semble pas être un bon ajustement. Honnêtement, je pense que vous feriez mieux de par l'utilisation de OAuth2. La mise en œuvre d'une coutume OAuth2 flux à l'appui de votre cas est une approche qui pourraient travailler, mais est très sujettes à erreur. Je n'ai pas travaillé avec OAuth2 encore, mais peut-être un "API key" peuvent être créés pour un utilisateur, de sorte qu'ils peuvent à la fois utiliser le front-end et back-end grâce à l'utilisation de clients mobiles.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X