Python Flaskでつくる LDAPログインページ

Mon, Dec 21, 2015

OpenLDAP と仲間たち Advent Calendar 2015 21日目。

Webアプリケーションを作る際、単純なログインページを作りたいことがよくあると思います。 しかし、認証ページというものはパスワード格納形式や、セッション管理、CSRF対策などいろいろな注意を払わなければなりません。

Python Flaskの認証ライブラリFlask-Loginを利用すると簡単に安全なログインページを作れます。

今回はこのFlask-LoginでLDAP認証する実装例を紹介します。

Flask-Loginの使い方

Flask-Login使うためには、

  1. LoginManagerの初期化
  2. ユーザークラスの定義
  3. フォームの定義

を行う必要があります。 今回は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にユーザー認証周りの実装例が詳しく書かれていますのでよかったら読んでみてください。

探検! Python Flask