如何使用Flask,Basic Auth with Ajax

时间:2015-07-23 02:08:44

标签: jquery python ajax flask flask-login

只是一个抬头,我是大多数人的新手,所以如果这是一个简单的主题,请提前抱歉。

我正在尝试使用基本身份验证设置一个简单的RESTful站点,以便使用Flask进行登录。我有一个页面(admin_panel),要求我登录和登录页面(登录)。如果我转到admin_panel,浏览器会弹出一个允许我输入用户名和密码的弹出窗口。一旦经过验证,我相信它会存储令牌,这样当我转到其他页面时,我不需要再次登录。一切正常。

在登录页面上,我不想弹出,所以我添加了ajax来使用Authentication Headers进行POST。这有效并成功登录。

一旦我使用登录页面登录然后转到admin_panel页面,浏览器会弹出另一个登录框。我相信这是因为我在执行ajax POST时没有存储令牌。

如何通过ajax以类似的方式将令牌存储到弹出窗口中?

这是login_auth javascript:

$(document).ready(function() {
    // bind the form submit event to our function
    $("#loginForm").bind('submit', function(e) {
        // prevent page refresh
        e.preventDefault();
        // post the data
        var username = $(this).find('input[name="username"]').val();
        var password = $(this).find('input[name="password"]').val();
        var ajax=$.ajax({
            type: "POST",
            dataType: 'json',
            encode: true,
            async: false,
            headers: {
                "Authorization": "Basic " + btoa(username + ":" + password)
            },
            data: { },
            url: "http://127.0.0.1:5000/api/login"
        }).done(function(data){
            console.log('Login Success!')
            location.reload();
        });
        ajax.fail(function(jqXHR, textStatus, errorThrown){
            console.log('error! '+jqXHR+' - '+textStatus+' - '+errorThrown);
        });
    });
});

以下是Flask页面:

#!/usr/bin/env python
import os
from flask import Flask, abort, request, jsonify, g, url_for, render_template
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.httpauth import HTTPBasicAuth
from passlib.apps import custom_app_context as pwd_context
from itsdangerous import (TimedJSONWebSignatureSerializer
                          as Serializer, BadSignature, SignatureExpired)
from functools import wraps

# initialization
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super duper easy secret key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydb.db'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

# extensions
db = SQLAlchemy(app)
auth = HTTPBasicAuth()


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), index=True, unique=True)
    password_hash = db.Column(db.String(64))
    user_role = db.Column(db.Enum('admin', 'user', name='user_role'))

    def hash_password(self, password):
        self.password_hash = pwd_context.encrypt(password)

    def verify_password(self, password):
        return pwd_context.verify(password, self.password_hash)

    def generate_auth_token(self, expiration=600):
        s = Serializer(app.config['SECRET_KEY'], expires_in=expiration)
        return s.dumps({'id': self.id})

    @staticmethod
    def verify_auth_token(token):
        s = Serializer(app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None    # valid token, but expired
        except BadSignature:
            return None    # invalid token
        user = User.query.get(data['id'])
        return user
@auth.verify_password
def verify_password(username_or_token, password):
    # first try to authenticate by token
    user = User.verify_auth_token(username_or_token)
    if not user:
        # try to authenticate with username/password
        user = User.query.filter_by(username=username_or_token).first()
        if not user or not user.verify_password(password):
            return False
    g.user = user
    return True

def verify_role(role):
    def decorator(func):
        @wraps(func)
        def decorated(*args, **kwargs):
            if g.user.user_role == role:
                return func(*args, **kwargs)
            else:
                abort(400) # incorrect role
        return decorated
    return decorator

@app.route('/api/users/<int:id>')
def get_user(id):
    user = User.query.get(id)
    if not user:
        abort(400)
    return jsonify({'username': user.username})


@app.route('/api/token')
@auth.login_required
def get_auth_token():
    token = g.user.generate_auth_token(600)
    return jsonify({'token': token.decode('ascii'), 'duration': 600})

@app.route('/login', methods=['GET', 'POST'])
def login():
    print request.method
    if request.method == 'POST':
        print 'posted', request.form.get('username'), request.form.get('password')
    else:
        return render_template('login.html')

@app.route('/api/login', methods=['POST'])
@auth.login_required
def _login():
   # HTTP Auth should do its thing
   return jsonify({"login": "success", "user": g.user.username})

@app.route('/admin/')
@auth.login_required
@verify_role('admin')
def admin_panel():
    return jsonify({'data': 'Hello, %s!' % g.user.username})

if __name__ == '__main__':
    if not os.path.exists('mydb.db'):
        db.create_all()
    app.run(debug=True)

我甚至不确定这是我应该这样做的方式。此外,目前正在本地使用,但如果它移动到其他地方,我最终会使用ssl。

提前致谢!

1 个答案:

答案 0 :(得分:0)

使用基本身份验证时,浏览器会存储单独的令牌。在Basic Auth中,每个请求都会发送用户名和密码。您的浏览器会自动为您执行此操作。

这也是您的身份验证方案无效的原因:浏览器不知道您之前的请求,也不存储凭据。因此,它会询问您何时从服务器收到下一个身份验证质询。由于安全原因,主要浏览器不会公开用于存储随机凭据的API,因此您无法更改此内容。

解决这个问题的唯一方法是不依赖于Basic Auth,而是依靠自己的身份验证机制。