← 返回文章流

Flask 博客接入第三方登录

在上一篇文章中我留了一部分内容,就是如何给评论登录接入第三方登录。我不希望来访问我博客的用户有太大的登录成本,否则本想留下些话的人,就会被挡在这个门槛之外。 Flask 不像 Django 一样有各种现成的组件可以选用,Flask 的各种扩展也不那么「开箱即用」。在我的博客项目中,我选用的是Authlib,它是国内的一名 Python 资深开发者@lepture开发的一款全面完善的 OAuth 认证库。大家可能在别的教程里会看到用的是flask-oauthlib,它们的作者其实是同一人,而且在 2019 年的今天,我绝对会推荐你用 Authlib 而不是 flask-oauthlib。 网上能搜索到的教程,有很多都已经过时,或者不那么「与时俱进」了,截止今天 Flask 已经到 1.1.1 版本了,而很多教程还停留在 0.10.x 时代[^1]。我是个喜欢与时俱进的人,我写的 Flask 相关文章,以及这个博客项目,保证都是基于最新的推荐,并会尽量保持更新。 [^1]: 比如Flask-Script这个扩展,我不推荐任何新的 Flask 项目使用,因为 Flask 从 0.11.0 开始已经内置了命令行的支持。 开发思路 首先我们要搞清楚我们需要第三方登录来做什么。很简单,获取用户的邮箱地址(用于通知)、用户头像、用户名称(用于展示)这些基本的信息。登录时,我们到对应的平台上获取令牌,然后通过此令牌去请求用户信息,存到我们的数据库里,以备后面使用。如果大家对 OAuth 不太了解的,OAuth 分为 OAuth1 协议与 OAuth2 协议,是一种开放的用户认证协议,它允许任何已注册的外部调用方(Client),获取平台(Provider)内部的授权访问的资源。OAuth2 协议更加简化些,我预备接入的 Github 和 Google 都属于这一种协议,认证的主要过程是: 接入过程 Github 的 OAuth2 接入是最简单的,很多教程都选择以 Github 为例,所以我这里选择用 Google 为例。 第一步,到Google API Console申请 OAuth2 凭据 选择 Web 应用,填入你的应用名称,和已获授权的重定向 URI,在上图中,当你确认授权访问以后,Google 会重定向到这个 URI 进行后续的动作。访问这个 URI 时会带上 code 的信息,一般地,这个 URI 的视图函数中应该做三件事情: 使用传入的 code 去 Google 交换访问令牌 存储访问令牌 使用访问令牌获取用户信息 完成了以后你就可以看到你的客户端 ID和客户端密钥了。 Authlib 的使用 安装过程就不用说了,用pip安装即可。先在models.py中加入一个新的表: class OAuth2Token(db.Model): id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(40)) token_type = db.Column(db.String(40)) access_token = db.Column(db.String(200)) refresh_token = db.Column(db.String(200)) expires_at = db.Column(db.Integer()) user_id = db.Column(db.Integer(), db.ForeignKey("user.id")) user = db.relationship("User", backref=db.backref("tokens", lazy="dynamic")) def to_token(self): return dict( access_token=self.access_token, token_type=self.token_type, refresh_token=self.refresh_token, expires_at=self.expires_at, ) 然后创建oauth对象: from authlib.integrations.flask_client import OAuth def fetch_token(name): token = OAuth2Token.query.filter_by(name=name, user=current_user).first() return token.to_token() def update_token(name, token, refresh_token=None, access_token=None): if refresh_token: item = OAuth2Token.filter_by(name=name, refresh_token=refresh_token).first() elif access_token: item = OAuth2Token.filter_by(name=name, access_token=access_token).first() else: return if not item: return # update old token item.access_token = token['access_token'] item.refresh_token = token.get('refresh_token') item.expires_at = token['expires_at'] db.session.commit() oauth = OAuth(fetch_token=fetch_token, update_token=update_token) google = oauth.register( name='google', access_token_url='https://www.googleapis.com/oauth2/v4/token', access_token_params={'grant_type': 'authorization_code'}, authorize_url='https://accounts.google.com/o/oauth2/v2/auth?access_type=offline', authorize_params=None, api_base_url='https://www.googleapis.com/', client_kwargs={'scope': 'email profile'} ) fetch_token和update_token两个函数是 Authlib 需要用来获取和更新令牌用的。然后,在配置文件中加入两个配置: GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') 因为这两个配置是敏感信息,推荐从环境变量读取,不要暴露在代码库中。记得在create_app中将oauth对象注册到Flask中: oauth.init_app(app) 好了,现在我们可以来写视图了: def google_login(): origin_url = request.headers['Referer'] session['oauth_origin'] = origin_url redirect_uri = url_for('.google_auth', _external=True) if not current_app.debug: redirect_uri = redirect_uri.replace('http://', 'https://') return google.authorize_redirect(redirect_uri) def google_auth(): token = google.authorize_access_token() # save token resp = google.get('oauth2/v3/userinfo') resp.raise_for_status() profile = resp.json() # save profile 注意到我在login函数中把request.headers['Referer']的值保存到了会话中,这是为了登录成功后跳转会原来的页面,而中途会跳转到外部的网址,所以需要把原地址记下来。跳转 google 认证地址的 URL 中需要包含回调的地址,而这个地址必须和之前在 Google API Console 中配置的地址一致(可以允许是子页面)。现在我们就可以使用第三方登录了。 进一步简化 大家可以发现这样使用我们必须知道 Google 的认证地址、令牌地址和一些额外请求参数,虽然我们可以查阅[Google OAuth 文档]获取这些信息,但这多少也是一种负担。所以 authlib 甚至提供一个库loginpass,包含几乎所有主流的 OAuth 提供方,使用 loginpass 以后,上面的三段代码可以替换成下面几行: from flask import Flask from authlib.integrations.flask_client import OAuth from loginpass import create_flask_blueprint, Google app = Flask(__name__) oauth = OAuth(app) def handle_authorize(remote, token, user_info): if token: save_token(remote.name, token) if user_info: save_user(user_info) return user_page raise some_error github_bp = create_flask_blueprint(Google, oauth, handle_authorize) app.register_blueprint(github_bp, url_prefix='/google') 我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=23bvqemu5etcw

来自 Frost's Blog作者主页
在原博客阅读全文 (新标签页打开)

以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。

一起聊聊 0

尊重不同观点

还没有评论。欢迎分享第一个想法。

RSS SUBSCRIPTIONS

选择 RSS 订阅

把博客汇的文章添加到你常用的阅读器。

关注文章 RSS 订阅本订阅需要登录后使用