奇人凡人の雑記帳

趣味とか投資とか、twitterに書きにくいことをこちらに書きます。毎週金曜更新目標(現在多忙のため月1の投資成績がメインです)

pythonanywhereにデプロイしたFlaskページにcaptchaを導入した話

h05torです。

前回の記事ではpythonanywhereにライブラリを追加した際の備忘録ということでしたが、その時に追加したライブラリはcaptchaというものを導入するために必要なライブラリでした。

というわけで、今回はFlaskページにcaptchaを導入した際の備忘録を書こうと思います。

 

まずcaptchaとは何かということですが、サイトにログインする際にたまに手書き文字みたいな崩れた数字の画像を読み取って数字を入力し、合っていないとログインできないみたいなのたまにありますよね。

f:id:h05tor:20211017211921p:plain←こんなのとか

 

あるいはその派生形でgoogleが提供しているrecaptchaというのもあって、パズルが出て「○○の画像をすべて選択してください」だったり「私はロボットではありません」っていうのをクリックする例のアレです。

 

captchaって何のためにあるのかといいますと、仮にIDやパスワードが外部に漏れて不正利用されるとき、大体botなどのプログラムを使ってサイトに侵入しようとします。

ここであえて認証に視覚情報など「人間でないと突破できない」情報を組みこむことでbot等からの攻撃を防げるというわけです。

 

(まあ最近は人工知能技術が進歩しているので私が導入した程度のものだと認識されてしまうでしょうが)

 

まあ、一応業務で使っている計算ツールが入っているFlaskページなのですが、多少業務内容を察されてしまうような用語も一部含まれているためあまり外部から簡単にはアクセスしてほしくはないのです。(別に企業秘密が入っているわけではないので見られたからまずいという程ではありませんが)

というわけで少しでも入りにくくするためにログイン画面にcaptchaを入れてみました。

 

↓のサイトを参照してみました。

flask-session-captcha · PyPI

 

もちろんpythonanywhereに導入する前に、ローカルでFlask動かしてやっています。

参照したサイトによると見た感じこのあたりのライブラリをimportしてますね。

from flask_sessionstore import Session
from flask_session_captcha import FlaskSessionCaptcha

参照元のサイトを丸パクするのもあれなので、captchaってあるぐらいだし、

flask_session_captchaだけインストールしてimportすればいいんじゃね?って思って実行してみたのですが、・・・・

f:id:h05tor:20211017222954p:plain

なんかよくわからないエラーが出ました。

 

なのでflask_sessionstoreの方もインストールして、importして再度実行したら問題なくうごきました。どうやら

・flask_session_captcha

・flask_sessionstore

は両方とも必須のようです。

ちなみにまた上記サイトからの引用になりますが

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SESSION_TYPE'] = 'sqlalchemy'

という部分も必須らしく、flask_sqlalchemyをimportしてないのに?と思ったのですがこの部分を省くとやはりエラーが出ました。↓

f:id:h05tor:20211017223610p:plain

 

引用サイト見るとimportには入っていませんが、使用環境にflask_sqlalchemyがインストールされている必要があるみたいで、どうやら表面上のプログラムでは使っていなくてもflask_session_captchaかflask_sessionstoreのどちらかが裏でflask_sqlalchemyのプログラムとつながっているのかもしれないです。

 

pythonanywhereにてflask_session_captchaとflask_sessionstoreはデフォルトでは入っていないので、Bash consoleから「pip3 install --user flask_session_captcha」というようにライブラリをインストールが必要です。

ちなみにflask_sqlalchemyはデフォルトで入っております。 

 

引用サイトを元にプログラムを書いてみました。

 

まず、webページを立ち上げるためpython

import uuid
from flask import Flask,render_template,request,session,redirect
from flask_sessionstore import Session
from flask_session_captcha import FlaskSessionCaptcha
from datetime import timedelta

app = Flask(__name__)
app.config["SECRET_KEY"] = uuid.uuid4()
app.config['CAPTCHA_ENABLE'] = True
app.config['CAPTCHA_LENGTH'] = 4
app.config['CAPTCHA_WIDTH'] = 160
app.config['CAPTCHA_HEIGHT'] = 60
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SESSION_TYPE'] = 'sqlalchemy'
app.permanent_session_lifetime = timedelta(minutes=3) 
Session(app)
captcha = FlaskSessionCaptcha(app)


@app.route("/",methods=['GET'])
def index():
    if "login" in session and session["login"]:
        return render_template("index.html")
    else:
        return redirect("/login")

@app.route("/login",methods=["GET"])
def login():
    return render_template("login.html",
                           title="SAMPLE PAGE",
                           err=False,
                           message="",
                           id="")


@app.route("/login",methods=["POST"])
def login_post():
    id = request.form.get("id")
    pswd = request.form.get("pass")

    if id == "XXXXXXXXXXX":#実際はデータベースで管理しています。
        if pswd == "XXXXXXXXXXXXXXXXXX":#実際はデータベースで管理しています。
            if captcha.validate():
                session["login"] = True
            else:
                session["login"] = False
        else:
            session["login"] = False
    else:
        session["login"] = False

    session["id"] = id
    if session["login"]:
        return redirect("/")
    else:
        return render_template("login.html",
                               title="Login",
                               err=True,
                               message="id or pass or captcha is different!",
                               id=id)

ID、パスワード、captcha全てクリアしていないとログインできないようになっています。

IDとパスワードはpythonプログラム内に直接正誤判定していますが、これはあくまで簡易的なものなのでセキュリティ的に滅茶苦茶脆弱なので、実際にはデータベース上で管理しております。

 

htmlの方でID、パスワードの入力欄とcaptchaを配置します。

<div class="card-body">
        <form method="post" action="/login">
            <div class="form-group">
                <label for="id">ログインID</label>
                <input type="text" class="form-control" id="id" name="id" value="{{id}}">
            </div>
            <div class="form-group">
                <label for="pass">ログインパスワード</label>
                <input for="password" class="form-control" id="pass" name="pass">
            </div>
            <div class="form-group">
                {{ captcha() }} 
                <br>
                <input type="text" name="captcha">
            </div>
            <div class="form-group">
                <input type="submit" value="Login">
            </div>
        </form>
    </div>

実際にpythonanywhereにアップしたのとは別のサンプルコードですが、こんな感じで作ってみました。

 

Flaskの追加コンテンツ的なのライブラリでcaptchaやってみましたけど、本当にセキュリティ万全にするのであればrecaptchaの方が良いのかもしれません。

そういえばpythonanywhereってrecaptcha使えるのかな?