前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Authlib 单点登录库初体验及踩坑

Authlib 单点登录库初体验及踩坑

作者头像
Ewdager
发布2020-07-14 14:52:12
1.6K0
发布2020-07-14 14:52:12
举报
文章被收录于专栏:Gvoidy备份小站

起因

项目突然要接入TX云,理所应当的要使用tx的单点登录了。于是乎,经过各方推荐,使用了大名鼎鼎的Authlib库。

初体验

经过各方文档,整理了一下,在Flask中使用Authlib相当简单。如果是接入有名的OAuth2站点如GithubGoogle这种,直接使用官方已经封装好的类即可快速实现,但此处使用的是TX方为工业互联网平台新搭建的OAuth2服务,理所应当不能直接使用。但仍可以使用较为便捷的封装进Flask中的认证方法,具体步骤如下:

新建存储Token的表

根据存储的access_token校验后续接口用户登录情况。 其中refresh_tokenaccess_token过期后,下次去OAuth2服务器请求新的Token的字段。如果在注册Authlib对象时写了update方法,即可自动更新token。

代码语言:javascript
复制
from app.models.base import db, Base


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_in = db.Column(db.Integer())
    user_id = db.Column(db.Integer(), db.ForeignKey("user.id"))
    user = db.relationship("User", backref=db.backref("auth_token", lazy="dynamic"))

    def to_token(self):
        return dict(
            access_token=self.access_token,
            token_type=self.token_type,
            refresh_token=self.refresh_token,
            expires_in=self.expires_in,
        )

    @staticmethod
    def save_token(response):
        with db.auto_commit():
            oauth = OAuth2Token()
            oauth.name = 'tencent'
            oauth.access_token = response['access_token']
            oauth.token_type = response['token_type']
            oauth.expires_in = response['expires_in'] if 'expires_in' in response else None
            oauth.refresh_token = response['refresh_token'] if 'refresh_token' in response else None
            oauth.user_id = response['user_id'] if 'user_id' in response else None
            db.session.add(oauth)

创建oauth对象

authlib会自动从flask的全局configapp/config/setting.py中读取你设置的{YOUR_NAME}_CLIENT_ID{YOUR_NAME}__CLIENT_SECRET

authorize_url中填写第一步跳转到单点登录接口的url,access_token_url填写从单点登录服务器拿到code和state后,换取token的地址。

代码语言:javascript
复制
from authlib.integrations.flask_client import OAuth
from app.models.oauth import OAuth2Token
from flask import g
from app.models.base import db

from app.config.setting import TENCENT_CLIENT_ID, TENCENT_CLIENT_SECRET


def fetch_token(name):
    token = OAuth2Token.query.filter_by(name=name, user_id=g.user.uid).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_in = token['expires_in']
    db.session.commit()


oauth = OAuth(fetch_token=fetch_token, update_token=update_token)

tencent = oauth.register(
    name='tencent',
    access_token_url=f'{CloudIndustryHost_internet}',
    access_token_params={
        'method': "GET",
        'grant_type': 'authorization_code'
    },
    authorize_url=f'{CloudIndustryHost_internet}',
    authorize_params=None,
    api_base_url=f'{CloudIndustryHost_internet}',
    client_kwargs={'scope': 'all'}
)

视图函数

代码语言:javascript
复制
from flask import url_for, redirect, make_response

from app.models.oauth import OAuth2Token
from app.libs.oauthlib import tencent
from . import oauth as api
from .utils import *


@api.route('/login', methods=['GET', 'POST'])
def login():
    redirect_uri = url_for("v1.oauth+auth", _external=True)
    return tencent.authorize_redirect(redirect_uri)


@api.route('/redirect', methods=['GET', 'POST'])
def auth():
    token_info = tencent.authorize_access_token()

    OAuth2Token.save_token(token_info)
    response = make_response(redirect('/'))
    response.set_cookies('session_id', token_info['access_token'])

将oauth对象注册进flask app

代码语言:javascript
复制
from app.libs.oauthlib import oauth


app = create_app()

oauth.init_app(app)

以上基本就能正常愉快的完成单点登录的全过程啦~

踩坑

好吧,实际上并不是这么一帆风顺的,在取得返回的code的视图函数中authorize_access_token()这一步一直碰到一个JSONDecodeError: Extra data: line 1 column 5 - line 1 column 19 (char 4 - 18)json无法序列化的bug。

经过进入Authlib的源码中深入调查,发现在fetch_token()这一步中,用OAuth服务器返回的code、states等参数向获取token的接口发请求时直接报404了。

反复查看文档发现地址并没有填错,最后发现,TX云那边使用的是GET方法拿token,而OAuthlibfetch_token()方法默认使用的是POST方法!!!

而要额外设置fetch_token()的方法需要再编写oath对象时,在access_token_params中加入所需要的方法。

authorize_access_token()方法会默认将access_token_params作为**kwarg传入后续的内部函数中,即可设置fetch_token()的参数。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 起因
  • 初体验
    • 新建存储Token的表
      • 创建oauth对象
        • 视图函数
          • 将oauth对象注册进flask app
          • 踩坑
          相关产品与服务
          访问管理
          访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档