8

Mein Ziel ist es, eine REST-API für meine Webanwendung bereitzustellen. Mit:Kombinieren Flask-unruhig, Flask-Sicherheit und regelmäßige Python-Anfragen

  • Python 2.7.5
  • Flask == 0.10.1
  • Flask-Restless == 0.13.1
  • Flask-Sicherheit == 1.7.3

I müssen den Zugriff auf meine Daten sowohl für den Web- als auch für den REST-Zugriff sicherstellen. Ich bin jedoch nicht in der Lage, einen regulären Python request erfolgreich auszuführen, wenn ich versuche, eine Verbindung mit der gesicherten API herzustellen.

Die folgenden Ausgaben werden mit dem voll funktionsfähigen Modul erhalten, das am Ende dieser Frage bereitgestellt wird.

schaffe ich eine richtige Antwort zu erhalten, wenn http://127.0.0.1:5000/api/v1/free_stuff mit:

>>> import requests 
>>> r=requests.get('http://127.0.0.1:5000/api/v1/free_stuff') 
>>> print 'status:', r.status_code 
status: 200 # all is fine 

Beim Versuch, die Authentifizierung mit http://127.0.0.1:5000/api/v1/protected_stuff:

>>> from requests.auth import HTTPBasicAuth, HTTPDigestAuth 
>>> r=requests.get('http://127.0.0.1:5000/api/v1/protected_stuff', 
        auth=HTTPBasicAuth('test', 'test')) # the same with ``HTTPDigestAuth`` 
>>> print 'status:', r.status_code 
status: 401 
>>> r.json() # failed! 
{u'message': u'401: Unauthorized'} 

hier ein Dummy-Funktionsmodul die obigen Ergebnisse verwendet wird, produzieren:

from flask import Flask, render_template, url_for, redirect 
from flask.ext.sqlalchemy import SQLAlchemy 
from flask.ext.security import Security, SQLAlchemyUserDatastore, \ 
    UserMixin, RoleMixin, login_required, current_user 
from flask.ext.restless import APIManager 
from flask.ext.restless import ProcessingException 

# Create app 
app = Flask(__name__) 
app.config['DEBUG'] = True 
app.config['SECRET_KEY'] = 'super-secret' 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' 

# Create database connection object 
db = SQLAlchemy(app) 

# Define Flask-security models 
roles_users = db.Table('roles_users', 
     db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), 
     db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) 

class Role(db.Model, RoleMixin): 
    id = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.String(80), unique=True) 
    description = db.Column(db.String(255)) 

class User(db.Model, UserMixin): 
    id = db.Column(db.Integer, primary_key=True) 
    email = db.Column(db.String(255), unique=True) 
    password = db.Column(db.String(255)) 
    active = db.Column(db.Boolean()) 
    confirmed_at = db.Column(db.DateTime()) 
    roles = db.relationship('Role', secondary=roles_users, 
          backref=db.backref('users', lazy='dynamic')) 
#Some additional stuff to query over... 
class SomeStuff(db.Model): 
    __tablename__ = 'somestuff' 
    id = db.Column(db.Integer, primary_key=True) 
    data1 = db.Column(db.Integer) 
    data2 = db.Column(db.String(10)) 
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) 
    user = db.relationship(User, lazy='joined', join_depth=1, viewonly=True) 

# Setup Flask-Security 
user_datastore = SQLAlchemyUserDatastore(db, User, Role) 
security = Security(app, user_datastore) 

# API 
def auth_func(**kw): 
    #import ipdb; ipdb.set_trace() 
    if not current_user.is_authenticated(): 
     raise ProcessingException(description='Not authenticated!', 
       code=401) 
    return True 
apimanager = APIManager(app, flask_sqlalchemy_db=db) 

apimanager.create_api(SomeStuff, 
    methods=['GET', 'POST', 'DELETE', 'PUT'], 
    url_prefix='/api/v1', 
    collection_name='free_stuff', 
    include_columns=['data1', 'data2', 'user_id']) 

apimanager.create_api(SomeStuff, 
    methods=['GET', 'POST', 'DELETE', 'PUT'], 
    url_prefix='/api/v1', 
    preprocessors=dict(GET_SINGLE=[auth_func], GET_MANY=[auth_func]), 
    collection_name='protected_stuff', 
    include_columns=['data1', 'data2', 'user_id']) 

# Create a user to test with 
@app.before_first_request 
def create_user(): 
    db.create_all() 
    user_datastore.create_user(email='test', password='test') 
    user_datastore.create_user(email='test2', password='test2') 
    ### 
    stuff = SomeStuff(data1=2, data2='toto', user_id=1) 
    db.session.add(stuff) 
    stuff = SomeStuff(data1=5, data2='titi', user_id=1) 
    db.session.add(stuff) 
    db.session.commit() 

# Views 
@app.route('/') 
@login_required 
def home(): 
    return render_template('index.html') 

@app.route('/logout/') 
def log_out(): 
    logout_user() 
    return redirect(request.args.get('next') or '/') 


if __name__ == '__main__': 
    app.run() 

Irgendeine Idee?

[Bearbeiten] sein, voll funktionsfähige über Web-Interface, können Sie einen templates Unterordner mit mindestens die folgenden login.html Datei haben müssen:

{% block body %} 
    <form action="" method=post class="form-horizontal"> 
    <h2>Signin to FlaskLogin(Todo) Application </h2> 
    <div class="control-group"> 
     <div class="controls"> 
      <input type="text" id="username" name="username" class="input-xlarge" 
      placeholder="Enter Username" required> 
     </div> 
    </div> 

    <div class="control-group"> 
     <div class="controls"> 
      <input type="password" id="password" name="password" class="input-xlarge" 
      placeholder="Enter Password" required> 
     </div> 
    </div> 

    <div class="control-group"> 
     <div class="controls"> 
      <button type="submit" class="btn btn-success">Signin</button> 
     </div> 
    </div> 
    </form> 
{% endblock %} 

Antwort

15

Ich ging schließlich zu Flask-JWT (https://pypi.python.org/pypi/Flask-JWT/0.1.0)

Hier ist meine modifizierte minimal Beispiel:

from flask import Flask, render_template, request, url_for, redirect 
from flask.ext.sqlalchemy import SQLAlchemy 
from flask.ext.security import Security, SQLAlchemyUserDatastore, \ 
    UserMixin, RoleMixin, login_required, current_user, logout_user 
from flask.ext.restless import APIManager 
from flask.ext.restless import ProcessingException 
from flask.ext.login import user_logged_in 
# JWT imports 
from datetime import timedelta 
from flask_jwt import JWT, jwt_required 

# Create app 
app = Flask(__name__) 
app.config['DEBUG'] = True 
app.config['SECRET_KEY'] = 'super-secret' 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' 
# expiration delay for tokens (here is one minute) 
app.config['JWT_EXPIRATION_DELTA'] = timedelta(seconds=60) 

# Create database connection object 
db = SQLAlchemy(app) 

# creates the JWT Token authentication ====================================== 
jwt = JWT(app) 
@jwt.authentication_handler 
def authenticate(username, password): 
    user = user_datastore.find_user(email=username) 
    print '%s vs. %s' % (username, user.email) 
    if username == user.email and password == user.password: 
     return user 
    return None 

@jwt.user_handler 
def load_user(payload): 
    user = user_datastore.find_user(id=payload['user_id']) 
    return user 

# Define Flask-security models =============================================== 
roles_users = db.Table('roles_users', 
     db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), 
     db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) 

class Role(db.Model, RoleMixin): 
    id = db.Column(db.Integer(), primary_key=True) 
    name = db.Column(db.String(80), unique=True) 
    description = db.Column(db.String(255)) 

class User(db.Model, UserMixin): 
    id = db.Column(db.Integer, primary_key=True) 
    email = db.Column(db.String(255), unique=True) 
    password = db.Column(db.String(255)) 
    active = db.Column(db.Boolean()) 
    confirmed_at = db.Column(db.DateTime()) 
    roles = db.relationship('Role', secondary=roles_users, 
          backref=db.backref('users', lazy='dynamic')) 
#Some additional stuff to query over... 
class SomeStuff(db.Model): 
    __tablename__ = 'somestuff' 
    id = db.Column(db.Integer, primary_key=True) 
    data1 = db.Column(db.Integer) 
    data2 = db.Column(db.String(10)) 
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) 
    user = db.relationship(User, lazy='joined', join_depth=1, viewonly=True) 
# Setup Flask-Security 
user_datastore = SQLAlchemyUserDatastore(db, User, Role) 
security = Security(app, user_datastore) 

# Flask-Restless API ========================================================== 
@jwt_required() 
def auth_func(**kw): 
    return True 

apimanager = APIManager(app, flask_sqlalchemy_db=db) 

apimanager.create_api(SomeStuff, 
    methods=['GET', 'POST', 'DELETE', 'PUT'], 
    url_prefix='/api/v1', 
    collection_name='free_stuff', 
    include_columns=['data1', 'data2', 'user_id']) 

apimanager.create_api(SomeStuff, 
    methods=['GET', 'POST', 'DELETE', 'PUT'], 
    url_prefix='/api/v1', 
    preprocessors=dict(GET_SINGLE=[auth_func], GET_MANY=[auth_func]), 
    collection_name='protected_stuff', 
    include_columns=['data1', 'data2', 'user_id']) 

# Create some users to test with 
@app.before_first_request 
def create_user(): 
    db.create_all() 
    user_datastore.create_user(email='test', password='test') 
    user_datastore.create_user(email='test2', password='test2') 
    ### 
    stuff = SomeStuff(data1=2, data2='toto', user_id=1) 
    db.session.add(stuff) 
    stuff = SomeStuff(data1=5, data2='titi', user_id=1) 
    db.session.add(stuff) 
    db.session.commit() 

# Views 
@app.route('/') 
@login_required 
def home(): 
    print(request.headers) 
    return render_template('index.html') 

@app.route('/logout/') 
def log_out(): 
    logout_user() 
    return redirect(request.args.get('next') or '/') 

if __name__ == '__main__': 
    app.run() 

Dann mit ihm überrequests zu interagieren:

>>> import requests, json 
>>> r=requests.get('http://127.0.0.1:5000/api/v1/free_stuff') # this is OK 
>>> print 'status:', r.status_code 
status: 200 
>>> r=requests.get('http://127.0.0.1:5000/api/v1/protected_stuff') # this should fail 
>>> print 'status:', r.status_code 
status: 401 
>>> print r.json() 
{u'status_code': 401, 
u'description': u'Authorization header was missing', 
u'error': u'Authorization Required'} 
>>> # Authenticate and retrieve Token 
>>> r = requests.post('http://127.0.0.1:5000/auth', 
...:     data=json.dumps({'username': 'test', 'password': 'test'}), 
...:     headers={'content-type': 'application/json'} 
...:     ) 
>>> print 'status:', r.status_code 
status: 200 
>>> token = r.json()['token'] 
>>> # now we have the token, we can navigate to restricted area: 
>>> r = requests.get('http://127.0.0.1:5000/api/v1/protected_stuff', 
...:     headers={'Authorization': 'Bearer %s' % token}) 
>>> print 'status:', r.status_code 
status: 200 
+3

Ich erweiterte dieses Beispiel und machte ein komplettes Beispielprojekt mit diesem Ansatz einschließlich Stubs zum Testen und Templating: https://github.com/graup/flask-restless-security Ich versuche auch, alle Pakete zu behalten auf dem Laufenden. – graup

1

Ihre ursprüngliche Abfrage (die Verwendung von Modul Python-Anfragen) half ich unstuck bekommen :) ich nicht anders nichts getan.

ich nicht Flask-Restless bin mit (noch)

FWIW - ich konnte die Auth-Token bekommen "nur" Flask-Sicherheit (dh ohne Flask-jwt verwenden)

See here für Details

+0

Guter Anruf, den CSRF-Token herunterzufahren! Großer Schmerz!:) Von dem, was ich sagen konnte, könnte die Verwendung von JWTs eine Alternative sein (wie in der Antwort oben erwähnt), aber das Speichern von serverseitigen Authtoken ist ziemlich praktisch für Ungültigkeitszwecke. – SJoshi

+0

Ich habe auch damit gekämpft, Postman mit Flask-Security laufen zu lassen. Habe es gerade wie erwartet funktioniert (sorta). Ich setze meinen Inhaltstyp auf application/json, mache meinen Endpunkt/login und gebe dann eine 'rohe' Daten vom Typ JSON ein und gebe dann meine Rohdaten als JSON (Email/Passwort) ein. Es funktioniert, gibt mir ein JSON-Token einmal zurück, aber von dort an, wenn ich eingeloggt bin, wird es meine index.html Seite zurückspucken. Um das Token wieder zu bekommen, muss ich mich/logout und/login erneut ... Kinda lame. Ich denke, es muss irgendwie Cookies senden, aber POSTman sollte diese nicht verwenden. – SJoshi

+0

Flask-Security bietet mehr als einen Authentifizierungs-Token-Mechanismus (der von Flask-JWT bereitgestellt wird). Es kann also eine Option für viele andere Dinge sein https://pythonhosted.org/Flask-Security/. Kannst du mich auf richtige URLs verweisen, wo ich die expiration_time für das auth_token setzen kann? Danke für deinen Blogpost übrigens – Hussain