Google云终端的自定义身份验证(而不是OAuth2) [英] Custom Authentication for Google Cloud Endpoints (instead of OAuth2)

查看:127
本文介绍了Google云终端的自定义身份验证(而不是OAuth2)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们对App Engine对 Google Cloud Endpoints 的支持感到非常兴奋。



即表示我们尚未使用OAuth2,并且通常使用用户名/密码
对用户进行身份验证,以便我们可以支持没有Google帐户的用户。



我们希望将我们的API迁移到Google Cloud Endpoints,因为我们可以免费获得所有好处(API控制台,客户端库,稳健性...),但是我们的主要问题...

如何将自定义身份验证添加到我们之前在现有API中检查有效用户会话+ CSRF令牌的云端点。



有没有一种优雅的方式可以做到这一点,而无需将会话信息和CSRF令牌等内容添加到protoRPC消息中?

我正在为我的整个应用程序使用webapp2身份验证系统。所以我试着重复使用Google Cloud Authentication,并获得它!



webapp2_extras.auth使用webapp2_extras.sessions存储auth信息。它可以以三种不同的格式存储:securecookie,datastore或memcache。

Securecookie是默认格式,我正在使用它。我认为它足够安全,因为webapp2认证系统用于在生产环境中运行的很多GAE应用程序。所以我解码这个securecookie并从GAE端点重新使用它。

我不知道这是否会产生一些安全问题(我希望不会),但也许@bossylobster可以说,如果它可以看安全方面。



我的Api:

 导入Cookie 
导入记录
导入终端
导入os
from google .appengine.ext从protorpc导入ndb
导入远程
从webapp2_extras.sessions导入时间
从web.frankcrm_api_messages导入SessionDict
导入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 = {$ b $'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_SECR ET_KEY ='9C3155​​EFEEB9D9A66A22EDC16AEDA'


@ 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):
返回一个基于用户对象的字典。

必须在此模块的
配置中设置要检索的额外属性。

:参数用户:
用户对象:自定义用户模型的实例。
:返回:
包含用户数据的字典。

如果不是用户:
返回无

用户代码= dict((a,getattr(user,a))for [])
user_dict ['user_id'] = user.get_id()
返回user_dict

@classmethod
def get_user_by_auth_token(cls,user_id,token):
基于user_id和auth令牌返回用户字典。

:param user_id:
用户ID。
:param token:
身份验证令牌。
:returns:
一个元组``(user_dict,token_timestamp)``。这两个值都可以是None。
如果用户无效或者
有效,但令牌需要续订,令牌时间戳将为无。

user,ts = User.get_by_auth_token(user_id,token)
返回cls.user_to_dict(用户),ts

@classmethod
def validate_token(cls,user_id,token,token_ts = None):
验证令牌。

令牌是用于临时验证的随机字符串。它们是用于验证会话或服务请求的


:param user_id:
用户ID。
:参数标记:
要检查的标记。
:param token_ts:
用于预验证令牌年龄的可选令牌时间戳。
:返回:
一个元组``(user_dict,token)``。

now = int(time.time())
delete = token_ts和((now - token_ts)> TOKEN_CONFIG ['token_max_age'])
create = False

如果不删除:
#尝试获取用户
user,ts = cls.get_user_by_auth_token(user_id,token)
如果用户:
#现在验证真实时间戳
delete =(now - ts)> TOKEN_CONFIG ['token_max_age']
create =(now - ts)> TOKEN_CONFIG ['token_new_age']

如果删除或创建或不创建用户:
如果删除或创建:
#删除db中的令牌
User.delete_auth_token(user_id,令牌)

如果删除:
user = None

令牌=无

返回用户,令牌

@ endpoints.method(IdContactMsg,ContactList ,
path = (self,request):

self.get_user_from_cookie()

如果不是self.user:
raise endpoints.UnauthorizedException('Invalid token。')

model_list = Contact.query()。fetch(20)
contact_list = []
for model_list中的联系人:
contact_list.append(contact.to_full_contact_message())

返回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()

如果不是self.user:
raise endpoints.UnauthorizedException('Invalid token。')


new_contact = Contact.put_f rom_message(请求)

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

返回IdContactMsg(id = new_contact.key.id())

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

如果不是self.user:
raise endpoints.UnauthorizedException('Invalid token。')


new_contact = Contact.put_from_message(请求)

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

返回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_coo kie()

如果不是self.user:
raise ends.UnauthorizedException('Invalid token。')

$ b $如果request.id:
contact_to_delete_key = ndb.Key(Contact,request.id)
如果contact_to_delete_key.get():
contact_to_delete_key.delete()
返回SimpleResponseMsg(success = True)

return SimpleResponseMsg(success = False)


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


We are super excited about App Engine's support for Google Cloud Endpoints.

That said we don't use OAuth2 yet and usually authenticate users with username/password so we can support customers that don't have Google accounts.

We want to migrate our API over to Google Cloud Endpoints because of all the benefits we then get for free (API Console, Client Libraries, robustness, …) but our main question is …

How to add custom authentication to cloud endpoints where we previously check for a valid user session + CSRF token in our existing API.

Is there an elegant way to do this without adding stuff like session information and CSRF tokens to the protoRPC messages?

解决方案

I'm using webapp2 Authentication system for my entire application. So I tried to reuse this for Google Cloud Authentication and I get it!

webapp2_extras.auth uses webapp2_extras.sessions to store auth information. And it this session could be stored in 3 different formats: securecookie, datastore or memcache.

Securecookie is the default format and which I'm using. I consider it secure enough as webapp2 auth system is used for a lot of GAE application running in production enviroment.

So I decode this securecookie and reuse it from GAE Endpoints. I don't know if this could generate some secure problem (I hope not) but maybe @bossylobster could say if it is ok looking at security side.

My 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)

这篇关于Google云终端的自定义身份验证(而不是OAuth2)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆