8

Amacım web uygulamasına bir REST API sağlamaktır. Kullanımı:Flask-huzursuz, Flask-güvenlik ve normal Python isteklerini birleştirmek

  • Python 2.7.5
  • Matara == 0.10.1
  • Flask-Huzursuz == 0.13.1
  • Flask-Güvenlik == 1.7.3

I Hem web hem de REST erişimi için verilerime erişimi güvenceye almalıyım. Ancak, güvenli API'ye bağlanmaya çalışırken normal python request başarılı olamıyorum.

Aşağıdaki çıktılar, bu sorunun sonunda sağlanan tam işlevli modül kullanılarak elde edilir. http://127.0.0.1:5000/api/v1/free_stuff kullanırken

Ben doğru cevabı almak için yönetmek:

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

http://127.0.0.1:5000/api/v1/protected_stuff ile kimlik doğrulaması çalışırken: Burada

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

yukarıdaki sonuçlar üretmek için kullanılan bir kukla fonksiyonel modüldür:

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

Herhangi bir fikir?

[değiştir], yaşadığınız en azından aşağıdaki login.html dosyası templates alt klasör olması gerekir web arayüzü üzerinden tamamen işlevsel olması için:

{% 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 %} 

cevap

15

Sonunda Flask-JWT gitti (https://pypi.python.org/pypi/Flask-JWT/0.1.0)

İşte

benim değiştirilmiş minimal örnektir:

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

Ardından,requests aracılığı etkileşime: (piton istekleri modülünü kullanarak)

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

Bu örneği genişlettik ve bu yaklaşımı kullanarak test ve şablonlama için ipuçları da dahil olmak üzere tam bir örnek proje hazırladım: https://github.com/graup/flask-restless-security Ayrıca tüm paketlerimi tutmaya çalışıyorum güncel. – graup

1

Orijinal sorgu bana farklı bir şey yapmadım :) ayrılmak olsun yardımcı oldu. Ben (henüz) Flask-Restless

FWIW kullanmıyorum

- kullanıyorum Yetkilendirme jetonu almak başardı "sadece" Flask-Güvenlik (yani-jwt Flask kullanmak zorunda kalmadan)

bakın here ayrıntılar için

+0

İyi bir çağrı CSRF belirtecini kapatıyor! Büyük acı!:) JWT'leri kullanarak, (yukarıdaki yanıtta belirtildiği gibi) bir alternatif olabilir, ancak sunucu tarafı authtokens depolamak, invalidasyon amaçları için oldukça kullanışlıdır. – SJoshi

+0

Ayrıca, Postman'ın Flask-Güvenlik ile çalışmasını sağlamakla da uğraşıyorum. Sadece beklendiği gibi çalışıyor (sorta). İçerik türümü uygulama/json olarak ayarlıyorum, son noktayı/girişimi yaptıktan sonra JSON türünde bir 'ham' veri giriyorum ve ham veriyi JSON (e-posta/şifre) olarak giriyorum. Çalışıyor, bana bir kez bir JSON belirteci veriyor, ancak oradan giriş yaptığımda, index.html sayfamı tüküreceğim. Simgeyi tekrar almak için,/logout ve/login yapmam gerekiyor ... Kinda lame. Bir şekilde kurabiye göndermesi gerekiyor, ama POSTman'ın bunları kullanmaması gerekiyordu. – SJoshi

+0

Flask-Security, bir auth jeton mekanizmasından daha fazlasını sağlar (Flask-JWT'nin sağladığı). Yani diğer birçok şey için bir seçenek olabilir https://pythonhosted.org/Flask-Security/. Beni auth_token için expiration_time ayarlayabileceğim doğru urllere yönlendirebilir misin? Bu arada blog mesajınız için teşekkürler – Hussain