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

Python
この記事は約20分で読めます。

こんにちは、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)
  • create_token() では、ユーザー名をもとにJWTトークンを生成しています。トークンは30分間有効で、秘密鍵(SECRET_KEY)を使って署名されています。
  • verify_token() は、JWTトークンを検証し、有効であればユーザー名を返します。トークンが期限切れや無効の場合は None を返します。
  • handle_login() 関数では、ユーザーがログインボタンを押した際にトークンが生成され、トークン情報が表示されます。

このコードでは、ユーザーがログインすると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)
  • token_input には、JWTトークンを入力します。verify_token() 関数でトークンが検証され、トークンが有効な場合は保護されたコンテンツが表示されます。
  • access_protected_content() 関数で、トークンの検証結果に応じて適切なメッセージが表示されます。

このコードにより、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()))
  • pn.state.user_logged_in で、ユーザーのログイン状態を保持しています。これにより、ユーザーがアプリケーション内で認証済みかどうかを追跡できます。
  • render_ui() 関数では、ユーザーがログインしているかどうかに基づいて異なるUIをレンダリングします。ログイン状態に応じて、ログアウトボタンまたはログインフォームを表示します。下の図はpn.state.user_logged_in == Falseの場合に表示される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()
  • CSRFProtect でFlaskアプリケーションにCSRF保護を追加し、すべてのPOSTリクエストに対してCSRFトークンを検証します。
  • pn.serve() でPanelアプリケーションをFlaskアプリ内でホストし、外部からのリクエストに対してセキュリティを強化します。

まとめ

後半では、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を表示する役割を持っています。

  • pn.state.user_logged_inTrueの場合(ログイン済み)、ログイン状態のメッセージと「Logout」ボタンが表示されます。このボタンをクリックするとlogout関数が呼び出され、ログアウト処理が行われます。
  • pn.state.user_logged_inFalseの場合(未ログイン)、ログインを促すメッセージと「Username」入力欄、そして「Login」ボタンが表示されます。この「Login」ボタンを押すと、login関数が呼び出されてユーザー名が「admin」として渡され、ログインが実行されます。

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()
  • jwt:JWT(JSON Web Token)を扱うためのライブラリです。このライブラリを使ってトークンの生成と検証を行います。
  • datetime:トークンの有効期限を設定するために使用します。
  • panel:Panelフレームワークをインポートし、pn.extension()でUIを有効化します。

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トークンを生成します。

  • expiration:トークンの有効期限を設定しています。この場合、現在時刻から30分後にトークンが期限切れになるように設定しています。
  • jwt.encode():トークンのペイロード(ここではユーザー名と有効期限)をSECRET_KEYで署名し、HS256アルゴリズムを使ってトークンを生成します。

この関数は、生成された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関数では、トークンが正しいかどうかを検証します。

  • jwt.decode():与えられたトークンを秘密鍵とアルゴリズムを使ってデコードします。この際、トークンの有効期限や署名の整合性を確認します。
  • もしトークンが期限切れの場合、ExpiredSignatureError例外が発生します。また、無効なトークンが与えられた場合は、InvalidTokenErrorが発生します。どちらの場合もNoneを返して、無効なトークンであることを示しています。

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

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

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

  • TextInputウィジェットは、ユーザー名を入力するためのテキストボックスを提供します。
  • Buttonウィジェットは、ユーザーがログインを試みるためのボタンです。
  • Markdownパネルは、JWTトークンを表示するためのテキストエリアです。初期状態では空の文字列に設定されています。

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関数は、ユーザーがログインボタンをクリックした際に呼び出されます。

  • create_token(username_input.value):ユーザーが入力したユーザー名を基にトークンを生成します。
  • token_message.objectに生成されたトークンを設定し、ユーザーにトークンを表示します。

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

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

layout = pn.Column(username_input, login_button, token_message)
layout.servable()
pn.serve(layout)
  • pn.Column()を使って、入力フィールド、ログインボタン、トークンメッセージを縦に並べてレイアウトしています。
  • layout.servable()で、このレイアウトをPanelアプリケーションとして公開します。
  • pn.serve()を呼び出すことで、アプリケーションがサーバー上で動作し、ブラウザ上にUIを表示するようになります。

まとめ

このコードは、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('')
  • TextInputウィジェット:ユーザーがJWTトークンを入力するためのテキストボックスです。このトークンを使って、コンテンツにアクセスできるかどうかを判定します。
  • Buttonウィジェット:保護されたコンテンツにアクセスするためのボタンです。
  • 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関数:
  • ボタンがクリックされたときに呼び出され、ユーザーが入力したトークンを検証します。
  • verify_token(token_input.value):ユーザーが入力したトークンをverify_token関数に渡し、トークンが有効かどうかを確認します。このverify_token関数は、JWTトークンをデコードして、その有効期限やユーザー情報を検証します。
  • トークンが有効であれば、そのトークンに紐付いたユーザー名を返します。
  • 無効なトークンや期限切れトークンの場合、Noneが返されます。
条件分岐:
  • userが有効であれば、”Welcome {user}, you have access to this protected content.” というメッセージがprotected_messageに表示され、ユーザーに保護されたコンテンツを提供します。
  • トークンが無効または期限切れの場合、”Invalid or expired token.”というエラーメッセージが表示されます。
イベントのバインド:
access_button.on_click(access_protected_content)
  • on_clickメソッドを使って、access_buttonがクリックされたときにaccess_protected_content関数が実行されるように設定します。

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

# Panelレイアウト
layout = pn.Column(token_input, access_button, protected_message)
layout.servable()
pn.serve(layout)
pn.Column:
  • token_input(トークンの入力ボックス)、access_button(アクセスボタン)、protected_message(結果表示)を縦に並べたレイアウトを作成します。
layout.servable():
  • レイアウトをPanelアプリケーションとして公開するためのメソッドです。このメソッドを呼び出すことで、UIがブラウザ上に表示されるようになります。
pn.serve():
  • Panelアプリケーションを実際にサーバーで動作させます。この行があることで、ユーザーはアプリケーションにアクセスして操作が可能になります。

コメント

タイトルとURLをコピーしました