Пользовательская аутентификация для конечных точек облака Google (вместо OAuth2)

Мы очень рады App Engine »поддержкаКонечные точки Google Cloud.

Тем не менее, мы неT еще не использует OAuth2 и обычно аутентифицирует пользователей с помощью имени пользователя / пароля, чтобы мы могли поддерживать клиентов, которые неу меня нет аккаунтов Google.

Мы хотим перенести наш API на конечные точки Google Cloud из-за всех преимуществ, которые мы затем получаем бесплатно (Консоль API, Клиентские библиотеки, надежность,…) но наш главный вопрос ...

Как добавить пользовательскую аутентификацию в облачные конечные точки, где мы предварительно проверяем действительный сеанс пользователя + токен CSRF в нашем существующем API.

Есть ли элегантный способ сделать это без добавления таких вещей, как информация о сеансе и токены CSRF, в сообщения protoRPC?

 PaulR19 сент. 2013 г., 18:45
Ничего нового сейчас, но я предоставил немного больше информации о том, как это сделать, но @tosh, я думаю, вы уже знали это.stackoverflow.com/questions/18716674/...I»
 bossylobster07 июн. 2013 г., 04:36
Будет работать над реальным ответом, но TL; DR, если вывы используете свои собственные аккаунты, выВам нужно будет создать свои собственные токены OAuth, если вы хотите использовать OAuth 2.0.
 M.Sameer18 сент. 2013 г., 17:46
Есть что-нибудь новое про Тош и @bossylobster? кто-нибудь сделал это успешно?

Ответы на вопрос(5)

JWT для аутентификации. РешенияВот

uthtopus, которая может заинтересовать любого, кто ищет решение этой проблемы:https://github.com/rggibson/Authtopus

Authtopus поддерживает базовые регистрации и имена пользователей и пароли, а также социальные учетные записи через Facebook или Google (возможно, можно добавить больше социальных провайдеров без особых хлопот). Учетные записи пользователей объединяются в соответствии с проверенными адресами электронной почты, поэтому, если пользователь сначала регистрируется по имени пользователя и паролю, а затем использует социальную учетную запись, и проверенные адреса электронной почты учетных записей совпадают, отдельная учетная запись пользователя не создается.

 Harikrishnan12 янв. 2016 г., 05:39
Хорошо. Я проверю что
 rggibson11 янв. 2016 г., 17:37
Хотя это может быть улучшено, есть некоторая информация о том, как работает библиотека, с подробностями об аргументах, ожидаемых в каждом URL конечной точки в README.
 rggibson09 янв. 2016 г., 19:59
Я хотел бы, но, вероятно, не собираюсь обойти это в ближайшее время.
 Harikrishnan08 янв. 2016 г., 10:06
Вы можете предоставить библиотеку для Java?
 Harikrishnan10 янв. 2016 г., 05:53
о хорошо может быть какая-то документация, чтобы я мог сделать библиотеку?

Когда сервер получает запрос на вход в систему, он ищет имя пользователя / пароль в хранилище данных. В случае, если пользователь не найден, сервер отвечает каким-либо объектом ошибки, который содержит соответствующее сообщение типа "Пользователь нене существует или как. В случае обнаружения он хранится в виде коллекции (кэша) FIFO с ограниченным размером, например 100 (или 1000 или 10000).

При успешном входе в систему сервер запросов возвращает клиенту sessionid вроде "; LKJLK345345LKJLKJSDF53KL», Может быть в кодировке Base64 имя пользователя: пароль. Клиент хранит его в Cookie с именем "authString» или же "идентификатор сессии" (или что-то менее красноречивое) с 30-минутным (любым) истечением.

С каждым запросом после входа клиент отправляет заголовок авторизации, который он берет из cookie. Каждый раз, когда файл cookie принимается, он обновляется - поэтому он никогда не истекает, пока пользователь активен.

На стороне сервера у нас будет AuthFilter, который будет проверять наличие заголовка авторизации в каждом запросе (исключая логин, регистрацию, сброс_пароля). Если такой заголовок не найден, фильтр возвращает ответ клиенту с кодом состояния 401 (клиент показывает экран входа в систему пользователю). Если фильтр заголовка найден, он сначала проверяет наличие пользователя в кеше, затем в хранилище данных, а если пользователь нашел - ничего не делает (запрос обрабатывается соответствующим методом), не найден - 401.

Вышеприведенная архитектура позволяет сохранять состояние сервера без сохранения состояния, но при этом автоматически отключать сеансы.

Google Cloud Endpoints предоставляет способ для реализации (RESTful?) API и создания библиотеки мобильного клиента. Аутентификация в этом случае будет OAuth2. OAuth2 предоставляет разныепотоковнекоторые из которых поддерживают мобильные клиенты. В случае аутентификации с использованием принципала и учетных данных (имя пользователя и пароль) это неКажется, это хорошо подходит. Честно говоря, я думаю, что вам будет лучше, если вы воспользуетесь OAuth2. Реализация пользовательского потока OAuth2 для поддержки вашего случая - это подход, который может работать, но очень подвержен ошибкам. У меня нетеще не работал с OAuth2, но возможноAPI ключ ' могут быть созданы для пользователя, чтобы они могли использовать как внешний интерфейс, так и внутренний с помощью мобильных клиентов.

 John14 сент. 2015 г., 02:57
OAuth2 всегда требует наличия учетной записи Google, которая является наиболее сложной проблемой для пользователя.
Решение Вопроса

Поэтому я попытался повторно использовать это для облачной аутентификации Google, и я понял!

webapp2_extras.auth использует webapp2_extras.sessions для хранения информации об аутентификации. И эта сессия может храниться в 3 разных форматах: securecookie, datastore или memcache.

Securecookie является форматом по умолчанию и который ям с помощью. Я считаю это достаточно безопасным, так как система аутентификации webapp2 используется для многих приложений GAE, работающих в производственной среде.

Поэтому я декодирую этот securecookie и повторно использую его с конечных точек GAE. Я неЯ не знаю, может ли это привести к какой-либо проблеме безопасности (надеюсь, что нет), но, возможно, @bossylobster мог бы сказать, нормально ли это с точки зрения безопасности.

Мой 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)
 Korneel17 дек. 2013 г., 11:34
Это также работает с форматом сеанса хранилища данных?
 CIF29 апр. 2014 г., 19:06
как установить cookie? Я не могне использовать webapp2 's auth.get_auth (). set_session () функция, поскольку глобальная переменная запроса не установлена.
 ZiglioUK16 янв. 2014 г., 02:01
Я использую обычную проверку подлинности на основе файлов cookie, и конечные точки, похоже, кэшируют файлы cookie для разных пользователей! Это'дает мне головную боль
 Douglas Correa17 дек. 2013 г., 17:23
Я думаю, что да, но тогда вы должны получить сессию из хранилища данных, а не от securecookie. Я попробовал это, но я не могне работает сессия хранилища данных
 Douglas Correa17 дек. 2013 г., 17:32
Теоретически, вам также нужен объект запроса для доступа к securecookie, но, как вы видите, я углубился в код webapp2 и обнаружил, что он на самом деле не нужен, просто некоторая информация из него. Может быть, вы могли бы сделать то же самое с сеансом Datastore
 user4737611 апр. 2015 г., 23:34
и как бы вы зарегистрироваться и войти?
 Korneel17 дек. 2013 г., 17:28
Я думаю, что проблема в том, что вам нужен объект Request для доступа к сеансу (формат хранилища данных). В конечных точках вы можетеДоступ к объекту запроса.

Ваш ответ на вопрос