有人可以解释使用其他 OAuth2 提供程序实现登录过程的步骤吗?此链接Google Cloud Endpoints 与另一个 oAuth2 提供程序几乎没有提供有关编写自定义身份验证的信息,但我想对于像我这样的初学者来说这还不够,请提供详细步骤。特别是对 Facebook 感兴趣。
5 回答
您需要根据 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 请求访问令牌。
所以我实际上尝试实现该自定义身份验证流程。尽管在安全方面可能会进一步考虑,但它似乎工作正常。
首先,用户转到我的应用程序并使用 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
笔记:
- 我正在使用这个库来处理服务器上的 facebook 身份验证检查:https ://github.com/pythonforfacebook/facebook-sdk
我通过添加一个 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
因此,没有人对 android 客户端的东西有所了解。由于在这种情况下您不需要 Google 登录,因此获取 api 句柄的代码如下所示:
private Api getEndpointsApiHandle() {
Api.Builder api = new Api.Builder(HTTP_TRANSPORT, JSON_FACTORY, null);
api.setRootUrl(yourRootUrl);
return api.build();
}
如果您注意到;您将需要将 null 作为凭据传递。这段代码就像一个魅力
我也为这个问题编写了自己的解决方案。你可以在这里查看代码:https ://github.com/rggibson/Authtopus
Authtopus 是一个 Python 库,用于使用 Google Cloud Endpoints 进行自定义身份验证。它支持基本的用户名和密码注册 + 登录,以及通过 Facebook 和 Google 登录(并且可以扩展以支持其他社交提供商而无需太多麻烦)。我知道这并不能直接回答原始问题,但它似乎足够相关,我想我会分享。