認証の仕組みと実装 | 認証とセキュリティ | Python 本格超入門 基本から発展へ

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

認証の仕組みと実装

Webサービスやモバイルアプリの裏側で動くAPI(Application Programming Interface)を開発する際、セキュリティの担保は最優先で取り組むべき課題です。

特に、誰がシステムにアクセスしているのかを確認する「認証」と、その人に実行権限があるかを制御する「認可」を適切に設計・実装しないと、重大なデータ漏洩や不正操作を招く脆弱性につながります。本記事では、API認証の基本概念から、現代のシステム開発で標準となっているJWT(JSON Web Token)を用いた実装方法までを解説します。

1. 認証の基本概念と3ステップ

API認証とは、リクエストの送信者が「本人であるか」「正当なシステム利用者であるか」を特定・検証するプロセスです。一般的なWeb APIでは、以下の3つのステップに沿って処理が進行します。

  1. 認証情報の送信: クライアント(ブラウザやスマホアプリなど)は、初回ログイン時にユーザー名とパスワードを暗号化(HTTPS)してサーバーへ送信します。
  2. 認証情報の検証: サーバー側は、受け取ったパスワードを安全な方法でハッシュ化し、データベース内に保管されているハッシュ値と照合します。
  3. 認証状態の永続化(トークンの発行): 照合が成功すると、サーバーはアクセスの許可証として「一意のトークン」を発行してクライアントに返します。次回以降、クライアントはこのトークンをリクエストのヘッダーに添付することで、毎回パスワードを送信することなく保護されたAPIにアクセスできるようになります。

2. 主なAPI認証方式の種類と特徴

システムの規模やセキュリティ要件に応じて、いくつかの認証方式が使い分けられます。

  • APIキー認証: 固有の長い文字列(キー)をリクエストに含めるだけのシンプルな方式です。手軽ですが、キーの漏洩リスクが高く、ユーザーごとの細かい権限管理が難しいため、公開されている天気予報APIなどの重要度の低いデータ取得によく使われます。
  • OAuth 2.0(オーオース): 「Googleでログイン」や「LINE連携」のように、サードパーティ製アプリに対してユーザーのパスワードを教えることなく、特定のデータへのアクセス権限を安全に委譲するための業界標準フレームワークです。
  • JWT(JSON Web Token): トークン自体の中に「ユーザーID」や「有効期限」などの情報が電子署名付きで埋め込まれている、データ密結合型のトークン認証方式です。サーバー側でセッション状態(ログイン中かどうかのデータ)をデータベースやメモリ(Redisなど)に保持して毎回照合する必要がない(ステートレスである)ため、アクセスが大量に集中するモダンなマイクロサービスやクラウド環境のAPI開発で最も好まれます。

3. Python(Flask)による基本認証の実装例

Pythonの軽量Webフレームワークである「Flask」を使い、ユーザー名とパスワードを用いた認証 APIの基本的な実装構造を示します。

from flask import Flask, request, jsonify
import hashlib

app = Flask(__name__)

# サンプルのユーザーデータベース
# セキュリティ上の絶対ルールとして、パスワードは生文(プレーンテキスト)のまま保存してはいけません。
# ここではあらかじめSHA-256でハッシュ化した値を登録しています。
users = {
    "user1": hashlib.sha256("password1".encode()).hexdigest(),
    "user2": hashlib.sha256("password2".encode()).hexdigest(),
}

@app.route('/login', methods=['POST'])
def login():
    # リクエストからBasic認証などの認証情報を取得
    auth = request.authorization
    
    if not auth or not auth.username or not auth.password:
        return jsonify({"message": "認証情報が不足しています"}), 401

    username = auth.username
    # 入力されたパスワードをハッシュ化して比較の準備をする
    input_password_hash = hashlib.sha256(auth.password.encode()).hexdigest()

    # ユーザー名が登録されており、かつハッシュ化したパスワードが一致するか検証
    if username in users and users[username] == input_password_hash:
        return jsonify({"message": f"ようこそ、{username}さん"}), 200
    else:
        # 認証失敗時は、一律で401 Unauthorizedステータスを返す(ハッカーへのヒントを減らすため)
        return jsonify({"message": "ユーザー名またはパスワードが正しくありません"}), 401

if __name__ == '__main__':
    app.run(debug=True)

4. JWT(JSON Web Token)認証の本格的実装

次に、実務で多用されるJWTを利用した認証システムの実装です。このコードを実行するには、外部ライブラリの PyJWT が必要となるため、あらかじめ pip install PyJWT でインストールを行ってください。

import jwt
import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)

# JWTの暗号化署名に使う秘密鍵(本番環境では必ず環境変数から取得し、厳重に秘匿してください)
app.config['SECRET_KEY'] = 'your_super_secret_key_string'

def generate_token(username):
    """ユーザー名と有効期限を含んだ暗号化トークンを発行する"""
    payload = {
        'username': username,
        # トークンの有効期限を「現在時刻から30分間」に設定
        'exp': datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=30)
    }
    # 秘密鍵とHS256アルゴリズムを使用してトークンを署名・生成
    token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
    return token

@app.route('/login', methods=['POST'])
def login():
    auth = request.authorization
    # 簡易的なユーザー検証
    if auth and auth.username == 'user1' and auth.password == 'password1':
        # 認証成功時にJWTを発行してクライアントに返す
        token = generate_token(auth.username)
        return jsonify({"token": token}), 200
    
    return jsonify({"message": "認証に失敗しました"}), 401

@app.route('/protected', methods=['GET'])
def protected_api():
    """有効なトークンを持っていないとアクセスできない保護されたAPI"""
    # リクエストヘッダーの 'Authorization' からトークンを回収
    token = request.headers.get('Authorization')

    if not token:
        return jsonify({"message": "トークン(許可証)が添付されていません"}), 401

    try:
        # 送られてきたトークンを秘密鍵で復号・検証
        # 改ざんされていないか、有効期限(exp)が切れていないかを自動チェックします
        data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        return jsonify({"message": f"認証成功。ようこそ{data['username']}さん。保護されたデータです。"}), 200
        
    except jwt.ExpiredSignatureError:
        return jsonify({"message": "トークンの有効期限が切れています。再度ログインしてください。"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"message": "トークンが不正、または改ざんされています。"}), 401

if __name__ == '__main__':
    app.run(debug=True)

主要な認証方式の比較・使い分けまとめ

API開発時にどの設計を選択するべきかの判断基準です。

認証方式 サーバー側での状態管理(セッション) 主なメリット 主なデメリット・注意点
APIキー認証 不要(データベースでキーを突合) 実装が非常に簡単。リクエストごとのオーバーヘッドが最小限。 キーが漏洩した際の被害が大きい。有効期限の設定や個別無効化が難しい。
従来のセッション認証 必要(メモリやDBにログイン状態を記録) サーバー側からいつでも強制的にログアウト(セッション破棄)を操作できる。 サーバーが複数台に増えた(スケールアウト)際、セッション情報の同期が必要になり管理が複雑化する。
JWT認証 完全に不要(ステートレス) トークン自体がデータを持つため、サーバー台数が増えても検証が容易。水平分散に強い。 一度発行したトークンは、有効期限が切れるまでサーバー側から強制失効させることが困難。

まとめ

  • パスワードは絶対にそのまま保存しない: 初期のハッシュ化ロジックの選定を含め、ユーザーのパスワードは hashlib などを経由させ、復元不可能なハッシュ値としてデータベースに保持する設計を徹底してください。
  • モダンなAPIにはJWTが最適: サーバー側のリソース(セッションメモリ)を圧迫せず、改ざん検知の仕組みがトークン自体に内蔵されているJWTは、現代のWebアプリケーション構築におけるスタンダードなアプローチです。
  • トークンの寿命と漏洩対策をセットにする: JWTはステートレスで便利な反面、盗まれた際の強制無効化が難しいため、有効期限(exp)を短く設定する、あるいはリフレッシュトークン(再発行用トークン)の仕組みと併用して運用する工夫が必要です。