flask-restful基于token认证遇到的问题记录

先把错误信息贴出来

127.0.0.1 - - [29/Nov/2017 09:13:32] "GET /admin/loginlogs?page_index=1 HTTP/1.1" 500 -
Traceback (most recent call last):
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "C:\Users\jyd\py3work\lib\site-packages\flask_cors\extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "C:\Users\jyd\py3work\lib\site-packages\flask_restful\__init__.py", line 273, in error_router
    return original_handler(e)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\jyd\py3work\lib\site-packages\flask_cors\extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "C:\Users\jyd\py3work\lib\site-packages\flask_restful\__init__.py", line 273, in error_router
    return original_handler(e)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\jyd\py3work\lib\site-packages\flask\app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\jyd\py3work\lib\site-packages\flask_restful\__init__.py", line 480, in wrapper
    resp = resource(*args, **kwargs)
  File "C:\Users\jyd\py3work\lib\site-packages\flask_httpauth.py", line 89, in decorated
    if not self.authenticate(auth, password):
  File "C:\Users\jyd\py3work\lib\site-packages\flask_httpauth.py", line 246, in authenticate
    return self.verify_token_callback(token)
  File "C:\Users\jyd\py3work\lib\site-packages\flask\views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "C:\Users\jyd\py3work\lib\site-packages\flask_restful\__init__.py", line 585, in dispatch_request
    assert meth is not None, 'Unimplemented method %r' % request.method
AssertionError: Unimplemented method 'GET'

部分代码

from flask_httpauth import HTTPTokenAuth
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

auth = HTTPTokenAuth(scheme='Bearer')
serializer = Serializer(app.config["SECRET_KEY"], expires_in=1800)


def create_token(data):
    """生成token"""
    token = serializer.dumps(data)
    return token.decode("utf-8")


@auth.verify_token
def verify_token(token):
    """验证token"""
    g.user = User.query.filter_by(token=token).first()
    return g.user is not None


class LoginApi(Resource):
    """登录接口"""

    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument('username', type=str, required=True, location=['json'])
        parse.add_argument('password', type=str, required=True, location=['json'])
        args = parse.parse_args()
        user = User.query.filter_by(username=args.username).first()
        if not user or not user.check_pwd(args.password):
            res = make_response(jsonify({"code": 1, "msg": "用户或密码错误"}))
            return res
        else:
            token = create_token({"username": user.username, "password": user.password})
            user.token = token
            db.session.add(user)
            db.session.commit()
            res = make_response(jsonify({"code": 0, "msg": "", "data": {"username": user.username,
                                                                        "nickname": user.nickname,
                                                                        "email": user.email,
                                                                        "phone": user.phone,
                                                                        "face": user.face,
                                                                        "mfa_status": user.mfa_status,
                                                                        "token": token}}))
            return res


class BankCardApi(Resource):
    """添加及修改银行卡接口"""
    decorators = [auth.verify_token]

    def post(self):
        verify = BankCardVerify()
        parse = reqparse.RequestParser()
        parse.add_argument('name', type=verify.name, required=True, location=['json'])
        parse.add_argument('card', type=verify.card, required=True, location=['json'])
        args = parse.parse_args()
        bankcard = BankCard(name=args.name, card=args.card, user_id=int(session.get("userid")))
        db.session.add(bankcard)
        db.session.commit()
        return jsonify({"code": 0, "msg": "添加成功"})



class LoginLogApi(Resource):
    """获取登录日志接口"""
    decorators = [auth.login_required]
    resource_fields = {'num_result': fields.Integer,
                       "objects": fields.List(fields.Nested({
                           'id': fields.Integer,
                           'user_id': fields.Integer,
                           'ip': fields.String,
                           'mfa_status': fields.Boolean,
                           'addtime': fields.String})),
                       "page": fields.Integer,
                       "total_page": fields.Integer
                       }

    @marshal_with(resource_fields, envelope='loginlog_resource')
    def get(self):
        parse = reqparse.RequestParser()
        parse.add_argument('page_index', type=int, required=True, location=['args'])
        args = parse.parse_args()
        login = Loginlog.query.filter_by(user_id=g.user.id).order_by(Loginlog.addtime.desc()).paginate(
            page=args.page_index, per_page=10)
        return {"num_result":login.total, "objects":login.items, "page":login.page, "total_page":login.pages}

restful.add_resource(BankCardApi, '/bankcard/add', endpoint='addbank')
restful.add_resource(LoginApi, '/admin/logins', endpoint='logins')
restful.add_resource(LoginLogApi, '/admin/loginlogs', endpoint='loginlogs')

主要代码已经贴上了,现在看看我怎么遇到那个问题的

我用postman先模拟登录

image.png

根据登录获取到的token,去获取该用户的登录日志,但是报错了,详细日志贴在开头

image.png

讲真遇到这个问题,不知道怎么解决,搞了好久才找到原因

分析:

用到了token认证,而且flask-restful要增加认证

#token认证
auth = HTTPTokenAuth(scheme='Bearer')
#flask-restful中增加的装饰器
decorators = [auth.login_required]

flask-httpauth部分源代码

    def login_required(self, f):
        @wraps(f)
        def decorated(*args, **kwargs):
            auth = request.authorization
            if auth is None and 'Authorization' in request.headers:
                print("Author")
                # Flask/Werkzeug do not recognize any authentication types
                # other than Basic or Digest, so here we parse the header by
                # hand
                try:
                    auth_type, token = request.headers['Authorization'].split(
                        None, 1)
                    auth = Authorization(auth_type, {'token': token})
                except ValueError:
                    # The Authorization header is either empty or has no token
                    pass

            # if the auth type does not match, we act as if there is no auth
            # this is better than failing directly, as it allows the callback
            # to handle special cases, like supporting multiple auth types
            if auth is not None and auth.type.lower() != self.scheme.lower():
                auth = None

            # Flask normally handles OPTIONS requests on its own, but in the
            # case it is configured to forward those to the application, we
            # need to ignore authentication headers and let the request through
            # to avoid unwanted interactions with CORS.
            if request.method != 'OPTIONS':  # pragma: no cover
                if auth and auth.username:
                    password = self.get_password_callback(auth.username)
                else:
                    password = None
                if not self.authenticate(auth, password):
                    # Clear TCP receive buffer of any pending data
                    request.data
                    return self.auth_error_callback()
            return f(*args, **kwargs)
        return decorated

class HTTPTokenAuth(HTTPAuth):
    def __init__(self, scheme='Bearer', realm=None):
        super(HTTPTokenAuth, self).__init__(scheme, realm)

        self.verify_token_callback = None

    def verify_token(self, f):
        self.verify_token_callback = f
        return f

    def authenticate(self, auth, stored_password):
        if auth:
            token = auth['token']
        else:
            token = ""
        if self.verify_token_callback:
            return self.verify_token_callback(token)
        return False

报错中有一部分内容是verify_token_callback

if not self.authenticate(auth, password):
  File "C:\Users\jyd\py3work\lib\site-packages\flask_httpauth.py", line 246, in authenticate
    return self.verify_token_callback(token)

http://flask-httpauth.readthedocs.io/en/latest/ 查看官方文档

image.png

再看看我这边回调的认证token函数,完全符合要求啊,难道函数没有生效

@auth.verify_token
def verify_token(token):
    """验证token"""
    g.user = User.query.filter_by(token=token).first()
    return g.user is not None

修改源文件进行调试


image.png

看到addbank我就觉得非常熟悉,且离找到原因不远了

restful.add_resource(BankCardApi, '/bankcard/add', endpoint='addbank') #addbank不就是在这里吗?
restful.add_resource(LoginApi, '/admin/logins', endpoint='logins')
restful.add_resource(LoginLogApi, '/admin/loginlogs', endpoint='loginlogs')

再看看这个

class BankCardApi(Resource):
    """添加及修改银行卡接口"""
    decorators = [auth.verify_token]   #这里写错了,不是verify_token,应该是login_required,不然token认证回调函数会被更改掉

    def post(self):
        verify = BankCardVerify()
        parse = reqparse.RequestParser()
        parse.add_argument('name', type=verify.name, required=True, location=['json'])
        parse.add_argument('card', type=verify.card, required=True, location=['json'])
        args = parse.parse_args()
        bankcard = BankCard(name=args.name, card=args.card, user_id=int(session.get("userid")))
        db.session.add(bankcard)
        db.session.commit()
        return jsonify({"code": 0, "msg": "添加成功"})

把代码改好后,再试试接口行不行
过期的token


image.png

生效的token

image.png

代码中用到的库文档:
http://flask-httpauth.readthedocs.io/en/latest/
http://flask-restful.readthedocs.io/en/latest/
http://pythonhosted.org/itsdangerous/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容