Google云terminal的自定义身份validation(而不是OAuth2)
我们对App Engine对Google Cloud Endpoints的支持感到非常兴奋。
这就是说,我们现在还不使用OAuth2,通常使用用户名/密码对用户进行身份validation,因此我们可以支持没有Google帐户的用户。
我们希望将我们的API迁移到Google Cloud Endpoints,因为我们可以免费获得所有好处(API控制台,客户端库,稳健性…),但我们的主要问题是…
如何将自定义身份validation添加到我们以前在现有API中检查有效用户会话+ CSRF令牌的云端点。
有没有一种优雅的方式来做到这一点,而不会像会话信息和CSRF令牌添加到protoRPC消息的东西?
我正在使用webapp2身份validation系统为我的整个应用程序。 所以我试着重复使用Google Cloud Authentication,我知道了!
webapp2_extras.auth使用webapp2_extras.sessions存储authentication信息。 而且这个会话可以以3种不同的格式存储:securecookie,datastore或者memcache。
Securecookie是我使用的默认格式。 我认为它足够安全,因为webapp2authentication系统用于在生产环境中运行的许多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)
根据我的理解,Google Cloud Endpoints提供了一种实现(RESTful?)API并生成移动客户端库的方法。 在这种情况下,身份validation将是OAuth2。 OAuth2提供了不同的“stream量”,其中一些支持移动客户端。 在使用主体和凭据(用户名和密码)进行身份validation的情况下,这看起来不太合适。 我真的认为你会更好使用OAuth2。 实现一个自定义的OAuth2stream程来支持你的情况是一种可行的方法,但是很容易出错。 我还没有使用OAuth2,但也许可以为用户创build一个“API密钥”,以便他们可以通过使用移动客户端使用前端和后端。
我写了一个名为Authtopus的自定义Python身份validation库,可能对任何想要解决此问题的人都感兴趣: https : //github.com/rggibson/Authtopus
Authtopus支持基本的用户名和密码注册和login,以及通过Facebook或Google的社交login(更多的社交供应商可能也可以添加没有太多的麻烦)。 用户帐户根据validation的电子邮件地址进行合并,因此,如果用户首先使用用户名和密码进行注册,然后使用社交login,并且validation的帐户的电子邮件地址匹配,则不会创build单独的用户帐户。
你可以使用jwt进行身份validation。 解决scheme
我还没有编码,但它想象下一个方法:
-
当服务器收到login请求时,在数据存储中查找用户名/密码。 如果用户没有find服务器响应包含适当的消息,如“用户不存在”或类似的一些错误对象。 如果发现它存储在FIFOtypes的收集(caching)有限的大小像100(或1000或10000)。
-
成功login请求服务器返回到客户端sessionid,如“; LKJLK345345LKJLKJSDF53KL”。 可以是Base64编码的用户名:密码。 客户端将其存储在名为“authString”或“sessionid”的cookie中(或者不太能说清楚),过期30分钟(任意)。
-
在login客户端发送每个请求后,它会从Cookie发送Autorization头。 每次使用cookie时,都会更新 – 所以在用户激活时它不会过期。
-
在服务器端,我们将有AuthFilter,它将检查每个请求中是否存在Authorization标头(不包括login,注册,reset_password)。 如果没有find这样的头文件,filter返回响应客户端状态代码401(客户端显示login屏幕给用户)。 如果发现头filter首先检查高速caching中用户的存在,在数据存储中后,如果用户发现 – 什么也不做(请求由适当的方法处理),找不到 – 401。
以上架构允许保持服务器无状态,但仍然有自动断开会话。