如何使用 Flask,Basic Auth 和 Ajax 在浏览器中存储令牌
How can I store a token in the browser using Flask, Basic Auth with Ajax
请注意,我对其中的大部分工作原理都不熟悉,如果这是一个简单的主题,请提前致歉。
我正在尝试设置一个简单的 RESTful 站点,使用 Flask 的基本身份验证进行登录。我有一个要求我登录的页面 (admin_panel) 和一个登录页面 (login)。如果我转到 admin_panel,浏览器会弹出一个窗口,让我输入我的用户名和密码。一旦经过验证,我相信它会存储一个令牌,这样当我转到其他页面时就不需要再次登录。这一切都有效。
在登录页面上,我不想弹出,所以我添加了 ajax 来执行 POST 和身份验证 Headers。这有效并成功登录。
我用登录页面登录,然后转到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。
提前致谢!
使用基本身份验证时,浏览器不会存储单独的令牌。在 Basic Auth 中,用户名和密码随每个请求一起发送。您的浏览器会自动为您执行此操作。
这也是您的身份验证方案不起作用的原因:浏览器不知道您之前的请求并且不存储凭据。因此,它会询问您何时从服务器收到下一个身份验证质询。您无法更改此设置,因为出于安全原因,主要浏览器不会公开 API 来存储随机凭据。
解决此问题的唯一方法是不依赖基本身份验证,而是采用您自己的身份验证机制。
请注意,我对其中的大部分工作原理都不熟悉,如果这是一个简单的主题,请提前致歉。
我正在尝试设置一个简单的 RESTful 站点,使用 Flask 的基本身份验证进行登录。我有一个要求我登录的页面 (admin_panel) 和一个登录页面 (login)。如果我转到 admin_panel,浏览器会弹出一个窗口,让我输入我的用户名和密码。一旦经过验证,我相信它会存储一个令牌,这样当我转到其他页面时就不需要再次登录。这一切都有效。
在登录页面上,我不想弹出,所以我添加了 ajax 来执行 POST 和身份验证 Headers。这有效并成功登录。
我用登录页面登录,然后转到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。
提前致谢!
使用基本身份验证时,浏览器不会存储单独的令牌。在 Basic Auth 中,用户名和密码随每个请求一起发送。您的浏览器会自动为您执行此操作。
这也是您的身份验证方案不起作用的原因:浏览器不知道您之前的请求并且不存储凭据。因此,它会询问您何时从服务器收到下一个身份验证质询。您无法更改此设置,因为出于安全原因,主要浏览器不会公开 API 来存储随机凭据。
解决此问题的唯一方法是不依赖基本身份验证,而是采用您自己的身份验证机制。