サイトアイコン アマチュア無線局JS2IIU

Panel応用編 第6回: 認証とセキュリティ (後半)

こんにちは、JS2IIUです。前回に続いてPanelを活用する上で重要な認証とセキュリティに関する話題です。前回の記事「Panel応用編 第6回: 認証とセキュリティ (前半)」も参考にしていただけたら幸いです。今回もよろしくお願いします。

はじめに

前半では、シンプルなログイン機能やOAuthを利用したGoogle認証をPanelアプリに実装する方法を紹介しました。後半では、より高度な認証手法であるJWT(JSON Web Tokens)を使ったトークンベース認証や、API保護、セッション管理について解説します。

JWTを使ったトークンベース認証

JWTは、JSON形式のデータを持つトークンを使って、ユーザー認証やAPIアクセス制御を行うための手法です。JWTは特にAPI認証で広く使われており、セキュアなトークンを使ってユーザーの認証状態を保持することができます。

手順1: pyjwtのインストール

まず、JWTを利用するためにpyjwtライブラリをインストールします。インストールは一瞬でした。

pip install pyjwt

手順2: JWTを使ったトークンの生成と検証

次に、ユーザーのログインに基づいてJWTトークンを発行し、そのトークンを使ってユーザーの認証を行うコードを実装します。

詳細な解説を本記事の最後に載せています。こちらから参照して下さい。

import jwt
import datetime
import panel as pn

pn.extension()

SECRET_KEY = "mysecretkey"

# トークンの生成
def create_token(username):
    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
    token = jwt.encode({"user": username, "exp": expiration}, SECRET_KEY, algorithm="HS256")
    return token

# トークンの検証
def verify_token(token):
    try:
        decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return decoded["user"]
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

# ログイン処理とトークンの表示
username_input = pn.widgets.TextInput(name='Username')
login_button = pn.widgets.Button(name='Login')
token_message = pn.pane.Markdown('')

def handle_login(event):
    token = create_token(username_input.value)
    token_message.object = f"Your JWT token: {token}"

login_button.on_click(handle_login)

# Panelレイアウト
layout = pn.Column(username_input, login_button, token_message)
layout.servable()
pn.serve(layout)

このコードでは、ユーザーがログインするとJWTトークンが生成され、認証に使用されるトークンが表示されます。

手順3: JWTを使ったAPIアクセス保護

JWTは、APIの保護にも広く使用されます。次に、JWTを使って保護されたAPIエンドポイントにアクセスする例を紹介します。このコードも本記事の最後に詳細な説明を書いていますので参照してみて下さい。

import panel as pn

pn.extension()

# トークンを使って保護されたコンテンツを表示
token_input = pn.widgets.TextInput(name='JWT Token')
access_button = pn.widgets.Button(name='Access Protected Content')
protected_message = pn.pane.Markdown('')

def access_protected_content(event):
    user = verify_token(token_input.value)
    if user:
        protected_message.object = f"Welcome {user}, you have access to this protected content."
    else:
        protected_message.object = "Invalid or expired token."

access_button.on_click(access_protected_content)

# Panelレイアウト
layout = pn.Column(token_input, access_button, protected_message)
layout.servable()
pn.serve(layout)

このコードにより、JWTトークンを用いたAPI保護を簡単に実装することができます。認証済みのユーザーだけが特定のコンテンツやAPIにアクセスできるようになります。

セッション管理とAPIの保護

Webアプリケーションでは、ユーザーがログインしている状態を保持し、セッションを管理する必要があります。Panelでは、セッション管理を行うためにいくつかの手段が用意されています。ここでは、セッション状態を管理し、APIの保護やユーザーセッションを制御する方法について説明します。

セッション管理の基本

Panelはセッションを持ち、セッション状態を変数に保持することができます。例えば、ユーザーがログイン状態かどうかを管理するためにセッションを使用することができます。こちらのサンプルコードについても詳細な解説を本記事の最後に載せていますので参照してみて下さい。

import panel as pn

pn.extension()

# ユーザーセッション状態を管理
pn.state.user_logged_in = False

# ログイン処理
def login(username):
    if username == "admin":
        pn.state.user_logged_in = True

# ログアウト処理
def logout():
    pn.state.user_logged_in = False

# セッションの状態を確認してUIを変更
def render_ui():
    if pn.state.user_logged_in:
        return pn.Column("You are logged in!", pn.widgets.Button(name="Logout", on_click=lambda event: logout()))
    else:
        return pn.Column("Please log in.", pn.widgets.TextInput(name="Username"), pn.widgets.Button(name="Login", on_click=lambda event: login("admin")))

# 初期UI表示
pn.Column(render_ui()).servable()
pn.serve(pn.Column(render_ui()))

CSRFトークンの導入

WebフォームやAPIリクエストに対して、CSRF(Cross-Site Request Forgery)攻撃から保護するための仕組みとして、CSRFトークンを使用します。Panelでは、外部ライブラリと組み合わせてCSRF保護を簡単に実装できます。

csrfライブラリを使ったCSRF保護

Pythonには、CSRFトークンをサポートするライブラリがあり、FlaskなどのWebフレームワークとも統合可能です。PanelアプリケーションがAPIを公開している場合、このようなCSRF保護を追加することが推奨されます。

pip install flask-wtf

以下は、Flaskと組み合わせてPanelアプリケーションでCSRF保護を実装する例です。

from flask import Flask
from flask_wtf.csrf import CSRFProtect
import panel as pn

# Flaskアプリの設定
app = Flask(__name__)
app.secret_key = 'supersecretkey'
csrf = CSRFProtect(app)

# Panelアプリケーションの定義
@csrf.exempt
@app.route('/')
def serve_panel():
    return pn.serve()

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

まとめ

後半では、JWTを使ったトークンベースの認証や、セッション管理、そしてAPI保護のためのCSRFトークンの導入について学びました。これらの手法を活用することで、Panelアプリケーションをよりセキュアにし、外部アクセスからの保護を強化できます。

これまでの内容を通じて、セキュリティが重要なPanelアプリケーション開発のポイントを理解し、実装できるようになったかと思います。次回は、データベースや外部サービスとの高度な統合について解説していきます。お楽しみに!

補足

補足(1) セッション管理とAPIの保護サンプルプログラムの解説

1. Panelの拡張機能を読み込む

import panel as pn

pn.extension()

まず、panelをインポートし、pn.extension()を呼び出しています。pn.extension()は、Panelのインタラクティブな要素(ウィジェットや拡張機能)を使うために必要な初期化処理です。これにより、PanelアプリケーションのインタラクティブUIを有効にします。さすがにもう慣れましたね。

2. ユーザーのセッション状態を管理

pn.state.user_logged_in = False

ここでは、pn.stateを使用して、ユーザーがログインしているかどうかの状態を管理します。pn.stateはセッションごとのデータを保持するためのオブジェクトで、複数のユーザーが同時にアプリケーションを使用する際に個々のセッションを管理するのに便利です。

pn.state.user_logged_inFalseに設定することで、初期状態ではユーザーがログインしていないことを示しています。

3. ログイン処理

def login(username):
    if username == "admin":
        pn.state.user_logged_in = True

このlogin関数は、ユーザーがログインしたときに呼び出されます。引数usernameに「admin」という文字列が渡された場合、pn.state.user_logged_inTrueに設定し、ログイン状態にします。今回は簡単な実装のため、ユーザー名のチェックだけでログインしていますが、実際のアプリケーションではパスワードチェックなども追加できます。

4. ログアウト処理

def logout():
    pn.state.user_logged_in = False

logout関数は、ユーザーがログアウトした際に呼び出されます。ここでは、pn.state.user_logged_inFalseに戻すことで、ログアウト状態にします。

5. UIの動的な更新

def render_ui():
    if pn.state.user_logged_in:
        return pn.Column("You are logged in!", pn.widgets.Button(name="Logout", on_click=lambda event: logout()))
    else:
        return pn.Column("Please log in.", pn.widgets.TextInput(name="Username"), pn.widgets.Button(name="Login", on_click=lambda event: login("admin")))

render_ui関数は、ログイン状態に応じて異なるUIを表示する役割を持っています。

UIはpn.Columnでレイアウトされています。pn.Columnは縦方向にコンポーネントを並べるためのレイアウトです。

6. 初期UIの表示

pn.Column(render_ui()).servable()

ここでは、render_ui関数によって生成されたUIをPanelアプリケーションとして表示しています。pn.Column(render_ui())でUIのレイアウトを定義し、servable()メソッドでそれをアプリケーションとして公開しています。

7. アプリケーションの起動

pn.serve(pn.Column(render_ui()))

最後に、pn.serve()を呼び出すことで、このPanelアプリケーションがサーバー上で動作するようになります。これにより、ブラウザ上でインターフェースを表示し、ユーザーがログインおよびログアウト操作を行えるようになります。


まとめ

このコードは、Panelフレームワークを使用して、シンプルなログイン機能を持つUIを作成する方法を示しています。pn.stateを使ったセッション管理、動的にUIを切り替える方法、そしてイベント駆動型のコールバックによるログイン処理がポイントです。

このプログラムは、基本的なログインUIを実装するための良い出発点であり、実際のアプリケーションではユーザー名やパスワードのチェック、データベースとの連携、セキュリティの強化などを追加することで、より高度なシステムに拡張できます。

JWTを使ったシンプルなトークン認証UIの解説

このコードは、PythonのPanelフレームワークとpyjwtライブラリを使用して、シンプルなトークン認証のUIを作成しています。JSON Web Token(JWT)を利用して、ユーザー名に基づいた認証トークンを生成し、認証されたユーザーを確認するシステムです。

今回のコードをプログラミング中級者向けに解説していきます。トークン生成、トークン検証、JWTの基本的な仕組み、Panelを用いたUI構築について詳しく説明します。

1. 必要なライブラリのインポート

import jwt
import datetime
import panel as pn

pn.extension()

pn.extension()は、PanelのインタラクティブUI要素を動作させるために必要な初期化処理です。

2. 秘密鍵の設定

SECRET_KEY = "mysecretkey"

JWTのトークン生成と検証に使う秘密鍵を定義しています。トークンを生成する際、SECRET_KEYを使用して署名を行い、トークンが改ざんされていないことを確認します。このキーは非常に重要で、安全に保管する必要があります。実際のコードでは直接書きません。。。

3. JWTトークンの生成

def create_token(username):
    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
    token = jwt.encode({"user": username, "exp": expiration}, SECRET_KEY, algorithm="HS256")
    return token

create_token関数では、ユーザー名を基にJWTトークンを生成します。

この関数は、生成されたJWTトークンを返します。

4. JWTトークンの検証

def verify_token(token):
    try:
        decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return decoded["user"]
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

verify_token関数では、トークンが正しいかどうかを検証します。

5. ユーザー名入力とログインボタンのUI

username_input = pn.widgets.TextInput(name='Username')
login_button = pn.widgets.Button(name='Login')
token_message = pn.pane.Markdown('')

ここでは、Panelのウィジェットを使ってログインフォームを作成しています。

6. ログインボタンのクリックイベント処理

def handle_login(event):
    token = create_token(username_input.value)
    token_message.object = f"Your JWT token: {token}"

login_button.on_click(handle_login)

handle_login関数は、ユーザーがログインボタンをクリックした際に呼び出されます。

login_button.on_click(handle_login)で、ログインボタンがクリックされたときにhandle_login関数を呼び出すよう設定しています。

7. Panelレイアウトの定義とアプリケーションの起動

layout = pn.Column(username_input, login_button, token_message)
layout.servable()
pn.serve(layout)

まとめ

このコードは、Panelフレームワークを用いて、シンプルなJWT認証を実装するための基本的なアプローチを示しています。ユーザーが「Username」を入力して「Login」ボタンを押すと、JWTトークンが生成されて表示されます。JWTの有効期限やトークンの検証処理も含まれており、基本的な認証システムの構築に応用可能です。

補足(3) JWTを使ったAPIアクセス保護サンプルコードの解説

このコードは、ユーザーが入力したJWT(JSON Web Token)を使って、保護されたコンテンツにアクセスできるシステムを構築しています。トークンが有効であれば、特定のコンテンツを表示し、無効または期限切れのトークンであればエラーメッセージを表示します。JWTの検証に基づいてコンテンツへのアクセスを制限するため、認証とセキュリティの観点から重要な部分を担っています。

1. 必要なライブラリのインポートと初期化

import panel as pn

pn.extension()

panelライブラリをインポートし、pn.extension()でPanelのインタラクティブUI機能を有効にします。これにより、ウィジェットやレイアウトなどを動作させる準備が整います。

2. ウィジェットの作成

# トークンを使って保護されたコンテンツを表示
token_input = pn.widgets.TextInput(name='JWT Token')
access_button = pn.widgets.Button(name='Access Protected Content')
protected_message = pn.pane.Markdown('')

3. トークンを検証して保護されたコンテンツにアクセス

def access_protected_content(event):
    user = verify_token(token_input.value)
    if user:
        protected_message.object = f"Welcome {user}, you have access to this protected content."
    else:
        protected_message.object = "Invalid or expired token."
access_protected_content関数:
条件分岐:
イベントのバインド:
access_button.on_click(access_protected_content)

4. Panelレイアウトとアプリケーションの起動

# Panelレイアウト
layout = pn.Column(token_input, access_button, protected_message)
layout.servable()
pn.serve(layout)
pn.Column:
layout.servable():
pn.serve():

モバイルバージョンを終了