flask-restful基于token認(rèn)證遇到的問題記錄

先把錯(cuò)誤信息貼出來

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):
    """驗(yàn)證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": "用戶或密碼錯(cuò)誤"}))
            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')

主要代碼已經(jīng)貼上了,現(xiàn)在看看我怎么遇到那個(gè)問題的

我用postman先模擬登錄

image.png

根據(jù)登錄獲取到的token,去獲取該用戶的登錄日志,但是報(bào)錯(cuò)了,詳細(xì)日志貼在開頭

image.png

講真遇到這個(gè)問題,不知道怎么解決,搞了好久才找到原因

分析:

用到了token認(rèn)證,而且flask-restful要增加認(rèn)證

#token認(rèn)證
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

報(bào)錯(cuò)中有一部分內(nèi)容是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

再看看我這邊回調(diào)的認(rèn)證token函數(shù),完全符合要求啊,難道函數(shù)沒有生效

@auth.verify_token
def verify_token(token):
    """驗(yàn)證token"""
    g.user = User.query.filter_by(token=token).first()
    return g.user is not None

修改源文件進(jìn)行調(diào)試


image.png

看到addbank我就覺得非常熟悉,且離找到原因不遠(yuǎn)了

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')

再看看這個(gè)

class BankCardApi(Resource):
    """添加及修改銀行卡接口"""
    decorators = [auth.verify_token]   #這里寫錯(cuò)了,不是verify_token,應(yīng)該是login_required,不然token認(rèn)證回調(diào)函數(shù)會(huì)被更改掉

    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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容