OpenLDAP と仲間たち Advent Calendar 2015 21日目。
Webアプリケーションを作る際、単純なログインページを作りたいことがよくあると思います。 しかし、認証ページというものはパスワード格納形式や、セッション管理、CSRF対策などいろいろな注意を払わなければなりません。
Python Flaskの認証ライブラリFlask-Loginを利用すると簡単に安全なログインページを作れます。
今回はこのFlask-LoginでLDAP認証する実装例を紹介します。
Flask-Loginの使い方
Flask-Login使うためには、
- LoginManagerの初期化
- ユーザークラスの定義
- フォームの定義
を行う必要があります。 今回はLDAPの認証サンプルを紹介しますが、他のデーターベースでも基本的にやることは変わりません。 ただしRDBMSやその他DBだとパスワードの格納方法など考えなければならないことは増えるでしょう。
準備
まずはFlaskとFlask-Login、Flask-WTF、python-ldapをインストール
$ pip install Flask Flask-Login Flask-WTF python-ldap
LoginManagerの登録
app = Flask(__name__)
login_manager = LoginManager()
login_manager.login_view = "login"
login_manager.init_app(app)
通常通りFlaskオブジェクトを生成したあとに、
LoginManager()を生成してアプリケーションに登録しているだけです。
login_manager.login_view
にログインページのviewを指定する必要があります。
ユーザークラスの定義
class User(object):
def __init__(self, username, data=None):
self.username = username
self.data = data
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return self.username
usernameしか持たない最小限のUserオブジェクトです、これらのメソッドはFlask-Loginに必要とされています。
このUserクラスのクラスメソッドとして認証コードを実装します。
これでログインフォームにuser
と入力するとcn=user,ou=Users,dc=example,dc=com
というDNでLDAP BINDを行います。
@classmethod
def auth(cls, username, password):
l = ldap.initialize(app.config['LDAP_URL'])
dn = 'cn=%s,ou=Users,dc=example,dc=com' % (username)
try:
l.simple_bind_s(dn, password)
except:
return None
return User(username)
また、認証後のユーザーオブジェクトを取得する関数を定義します。
LDAPならcnやsnなどの属性情報をSEARCHして取ってくるのが良いと思いますが今回は省略。
@login_manager.user_loader
def load_user(username):
return User(username)
フォームの定義
続いてログインフォームを定義します。
今回テンプレートを使わないので__str__
メソッドを定義していますが、通常はテンプレートを使ってフォームをレンダリングすると思います。
フォームにself.csrf_token
を埋め込んでいますが、たったこれだけでCSRF対策ができます。
class LoginForm(Form):
username = TextField('Username', validators=[validators.Required()])
password = PasswordField('Password', validators=[validators.Required()])
def __str__(self):
return '''
<form action="/login" method="POST">
<p>%s: %s</p>
<p>%s: %s</p>
%s
<p><input type="submit" name="submit" /></p>
</form>
''' % (self.username.label, self.username,
self.password.label, self.password,
self.csrf_token)
ログインビュー
ログインのビューはとてもシンプルです。
form.validate_on_submit()
で入力値のチェックとCSRFトークンの検証も行ってくれます。
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if form.validate_on_submit():
user = User.auth(form.username.data, form.password.data)
if user:
login_user(user)
print('ログイン成功')
return redirect(request.args.get('next', '/'))
else:
print('ログイン失敗')
return str(form) # フォームのレンダリング
CSRF対策を行う場合はapp.config['SECRET_KEY']
の設定を忘れないようにしてください。トークンを生成する際の鍵と成ります。
認証後のビュー
認証で保護したいビューは、デコレーターで@login_required
と書くだけです。
@app.route('/')
@login_required
def index():
return u'こんにちは! %s さん' % (current_user.username)
これで、トップページにアクセス -> ログインページにリダイレクト -> 認証後にトップページに戻るという一連の認証処理を実装できました。
全ソースコードはこちらです。
Flask-Loginの基本的な使い方だけを紹介しましたが、探検! Python Flaskにユーザー認証周りの実装例が詳しく書かれていますのでよかったら読んでみてください。