付録B トラブルシューティングガイド
B.1 認証が失敗する場合
B.1.1 パスワード認証の問題
症状:正しいパスワードでもログインできない
確認ポイント:
# デバッグコード例
def debug_password_auth(username, password):
print(f"1. ユーザー検索: {username}")
user = User.find_by_username(username)
if not user:
print(" → ユーザーが見つかりません")
return
print(f"2. アカウント状態: active={user.is_active}")
if not user.is_active:
print(" → アカウントが無効化されています")
return
print(f"3. パスワードハッシュ形式: {user.password_hash[:20]}...")
# ハッシュアルゴリズムの確認
if user.password_hash.startswith("$2b$"):
print(" → bcrypt形式")
elif user.password_hash.startswith("$argon2"):
print(" → Argon2形式")
print(f"4. パスワード検証実行")
is_valid = verify_password(password, user.password_hash)
print(f" → 結果: {is_valid}")
よくある原因と対策:
原因 | 対策 | 確認方法 |
---|---|---|
文字エンコーディング不一致 | UTF-8統一 | password.encode('utf-8') |
ハッシュアルゴリズム不一致 | 移行処理実装 | ハッシュプレフィックス確認 |
空白文字の扱い | trim()処理統一 | 前後空白の有無確認 |
大文字小文字の扱い | 仕様明確化 | ポリシー文書化 |
症状:パスワードリセットが機能しない
チェックリスト:
email_configuration:
- [ ] SMTPサーバー接続確認
- [ ] 送信元メールアドレスのSPF/DKIM設定
- [ ] メールテンプレートの変数展開
- [ ] リンクの有効期限設定
token_generation:
- [ ] トークンの一意性
- [ ] 適切な有効期限(推奨:1-2時間)
- [ ] 使用済みトークンの無効化
- [ ] タイムゾーンの扱い
security_checks:
- [ ] レート制限の実装
- [ ] 同一ユーザーの複数リクエスト制御
- [ ] トークンの暗号学的安全性
- [ ] HTTPSでのみ送信
B.1.2 多要素認証の問題
症状:TOTP認証で時刻ずれエラー
診断スクリプト:
import pyotp
import time
from datetime import datetime, timezone
def diagnose_totp_issue(secret, user_input_code):
"""TOTP問題の診断"""
totp = pyotp.TOTP(secret)
# 現在時刻での正しいコード
current_time = time.time()
correct_code = totp.at(current_time)
print(f"サーバー時刻: {datetime.now(timezone.utc)}")
print(f"正しいコード: {correct_code}")
print(f"入力コード: {user_input_code}")
# 時間窓での検証
for offset in range(-3, 4): # ±90秒
time_at_offset = current_time + (offset * 30)
code_at_offset = totp.at(time_at_offset)
if code_at_offset == user_input_code:
print(f"✓ コードは{offset * 30}秒のずれで一致")
return True
print("✗ 時間窓内で一致するコードなし")
# よくある問題の確認
if len(user_input_code) != 6:
print("! コードの桁数が正しくありません")
if not user_input_code.isdigit():
print("! コードに数字以外が含まれています")
return False
対策実装:
class FlexibleTOTPVerifier:
def __init__(self, window=1, future_window=0):
"""
window: 過去方向の許容ステップ数
future_window: 未来方向の許容ステップ数
"""
self.window = window
self.future_window = future_window
def verify(self, secret, token, for_time=None):
"""柔軟なTOTP検証"""
if for_time is None:
for_time = time.time()
# pyotpのverifyメソッドを使用(時間窓対応)
totp = pyotp.TOTP(secret)
# 通常の検証
if totp.verify(token, for_time, valid_window=self.window):
return True, 0
# 未来方向の検証(クライアント時刻が進んでいる場合)
if self.future_window > 0:
for i in range(1, self.future_window + 1):
future_time = for_time + (i * 30)
if totp.verify(token, future_time, valid_window=0):
return True, i * 30
return False, None
B.1.3 WebAuthn/FIDO2の問題
症状:ブラウザが認証器を認識しない
ブラウザ互換性チェック:
async function checkWebAuthnSupport() {
const report = {
supported: false,
create: false,
get: false,
platformAuthenticator: false,
conditionalMediation: false,
userVerifying: false
};
// 基本的なサポート
if (window.PublicKeyCredential) {
report.supported = true;
// create/getの確認
report.create = typeof navigator.credentials.create === 'function';
report.get = typeof navigator.credentials.get === 'function';
// プラットフォーム認証器
if (PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable) {
report.platformAuthenticator =
await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
}
// Conditional Mediation (パスキー自動入力)
if (PublicKeyCredential.isConditionalMediationAvailable) {
report.conditionalMediation =
await PublicKeyCredential.isConditionalMediationAvailable();
}
}
console.table(report);
return report;
}
// HTTPS確認
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.error('WebAuthnはHTTPS環境でのみ動作します');
}
一般的なエラーと対処法:
class WebAuthnErrorHandler {
handleError(error) {
const errorMap = {
'NotAllowedError': {
message: 'ユーザーが認証をキャンセルしました',
action: 'retry',
userMessage: 'もう一度お試しください'
},
'InvalidStateError': {
message: '認証器がすでに登録されています',
action: 'use_existing',
userMessage: 'この認証器は登録済みです'
},
'NotSupportedError': {
message: 'この認証器タイプはサポートされていません',
action: 'fallback',
userMessage: '別の認証方法をお試しください'
},
'SecurityError': {
message: 'セキュリティ要件を満たしていません',
action: 'check_https',
userMessage: 'HTTPS接続を確認してください'
},
'AbortError': {
message: '操作がタイムアウトしました',
action: 'retry',
userMessage: '時間内に認証を完了してください'
}
};
const errorInfo = errorMap[error.name] || {
message: error.message,
action: 'contact_support',
userMessage: 'エラーが発生しました'
};
console.error(`WebAuthn Error: ${error.name}`, error);
return errorInfo;
}
}
B.2 セッション管理の問題
B.2.1 セッションが維持されない
症状:ログイン直後にセッションが切れる
診断手順:
def diagnose_session_issue(request, response):
"""セッション問題の診断"""
issues = []
# 1. Cookieの設定確認
set_cookie_header = response.headers.get('Set-Cookie', '')
if 'SameSite=None' in set_cookie_header and 'Secure' not in set_cookie_header:
issues.append("SameSite=NoneにはSecure属性が必須")
if 'HttpOnly' not in set_cookie_header:
issues.append("HttpOnly属性が設定されていません(XSS脆弱性)")
# 2. ドメイン設定の確認
cookie_domain = extract_domain_from_cookie(set_cookie_header)
request_host = request.headers.get('Host', '')
if cookie_domain and not request_host.endswith(cookie_domain):
issues.append(f"Cookieドメイン({cookie_domain})とリクエストホスト({request_host})が不一致")
# 3. パス設定の確認
cookie_path = extract_path_from_cookie(set_cookie_header)
if cookie_path != '/':
issues.append(f"Cookieパスが制限されています: {cookie_path}")
# 4. プロキシ設定の確認
x_forwarded_proto = request.headers.get('X-Forwarded-Proto')
if x_forwarded_proto == 'http' and 'Secure' in set_cookie_header:
issues.append("プロキシ背後でHTTP通信しているがSecure Cookieを設定")
return issues
Cookieトラブルシューティング表:
問題 | 原因 | 解決策 |
---|---|---|
Safari/iOSで動作しない | SameSite=None未設定 | 明示的に設定 + Secure必須 |
サブドメイン間で共有されない | Domain属性なし | .example.com 形式で設定 |
APIコールで送信されない | CORS設定不備 | credentials: 'include' |
開発環境で動作しない | Secure属性 | localhost例外処理追加 |
B.2.2 分散環境でのセッション不整合
症状:ロードバランサー配下でログイン状態が安定しない
セッションストレージ診断:
class SessionStorageDiagnostics:
def __init__(self, redis_clients):
self.redis_clients = redis_clients # 複数のRedisノード
async def check_session_replication(self, session_id):
"""セッションレプリケーションの確認"""
results = {}
for node_name, client in self.redis_clients.items():
try:
# セッションデータの取得
session_data = await client.get(f"session:{session_id}")
if session_data:
results[node_name] = {
'exists': True,
'size': len(session_data),
'ttl': await client.ttl(f"session:{session_id}")
}
else:
results[node_name] = {'exists': False}
except Exception as e:
results[node_name] = {'error': str(e)}
# 整合性チェック
unique_sessions = set(
r.get('size', 0) for r in results.values()
if r.get('exists')
)
if len(unique_sessions) > 1:
print("警告: ノード間でセッションデータのサイズが異なります")
return results
async def test_session_persistence(self):
"""セッション永続性テスト"""
test_session_id = f"test_{uuid.uuid4()}"
test_data = {"user": "test", "timestamp": time.time()}
# 書き込みテスト
write_results = {}
for node_name, client in self.redis_clients.items():
start = time.time()
try:
await client.setex(
f"session:{test_session_id}",
300, # 5分
json.dumps(test_data)
)
write_results[node_name] = {
'success': True,
'latency': time.time() - start
}
except Exception as e:
write_results[node_name] = {
'success': False,
'error': str(e)
}
# レプリケーション待機
await asyncio.sleep(0.5)
# 読み取りテスト
read_results = await self.check_session_replication(test_session_id)
return {
'write_results': write_results,
'read_results': read_results,
'replication_ok': all(r.get('exists') for r in read_results.values())
}
B.3 パフォーマンス問題
B.3.1 認証処理が遅い
症状:ログインに数秒かかる
パフォーマンスプロファイリング:
import cProfile
import pstats
from io import StringIO
class AuthPerformanceProfiler:
def profile_authentication(self, username, password):
"""認証処理のプロファイリング"""
pr = cProfile.Profile()
pr.enable()
# 認証処理の実行
try:
result = authenticate_user(username, password)
finally:
pr.disable()
# 結果の解析
s = StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
ps.print_stats(20) # 上位20個の関数
# ボトルネックの特定
profile_data = s.getvalue()
# パスワードハッシュ処理の時間
bcrypt_time = self._extract_time(profile_data, 'bcrypt')
db_time = self._extract_time(profile_data, 'database')
recommendations = []
if bcrypt_time > 0.5:
recommendations.append({
'issue': 'bcryptコストファクターが高すぎる',
'current': bcrypt_time,
'recommendation': 'コストファクターを12に調整'
})
if db_time > 0.1:
recommendations.append({
'issue': 'データベースクエリが遅い',
'current': db_time,
'recommendation': 'インデックスの追加、コネクションプーリング'
})
return {
'total_time': pr.total_tt,
'profile': profile_data,
'recommendations': recommendations
}
最適化チェックリスト:
database_optimization:
- [ ] ユーザーテーブルのインデックス
- username/emailカラム
- 複合インデックスの検討
- [ ] N+1クエリの解消
- 権限情報の事前読み込み
- JOINまたはバッチ取得
- [ ] コネクションプーリング
- 適切なプールサイズ
- コネクション再利用
caching_strategy:
- [ ] ユーザー情報のキャッシュ
- TTL: 5-10分
- 更新時の無効化
- [ ] 権限情報のキャッシュ
- TTL: 1時間
- 役割変更時の無効化
- [ ] セッションストアの最適化
- Redisパイプライニング
- バッチ操作
hash_optimization:
- [ ] 適切なコストファクター
- bcrypt: 10-12
- argon2: メモリとCPUのバランス
- [ ] ハッシュ処理の非同期化
- ワーカースレッド使用
- イベントループのブロッキング回避
B.3.2 大量ログイン時の障害
症状:朝のラッシュ時にシステムダウン
負荷シミュレーション:
import asyncio
import aiohttp
import time
from datetime import datetime, timedelta
class LoadTestSimulator:
def __init__(self, base_url, total_users=10000):
self.base_url = base_url
self.total_users = total_users
self.results = []
async def simulate_morning_rush(self):
"""朝のログインラッシュをシミュレート"""
# 7:00-9:00の2時間で、ピークは8:00
async def user_login(user_id, delay):
await asyncio.sleep(delay)
start_time = time.time()
async with aiohttp.ClientSession() as session:
try:
async with session.post(
f"{self.base_url}/auth/login",
json={
"username": f"user{user_id}",
"password": "password"
},
timeout=aiohttp.ClientTimeout(total=30)
) as response:
end_time = time.time()
self.results.append({
'user_id': user_id,
'status': response.status,
'response_time': end_time - start_time,
'timestamp': datetime.now()
})
except asyncio.TimeoutError:
self.results.append({
'user_id': user_id,
'status': 'timeout',
'response_time': 30,
'timestamp': datetime.now()
})
except Exception as e:
self.results.append({
'user_id': user_id,
'status': 'error',
'error': str(e),
'timestamp': datetime.now()
})
# 正規分布でログイン時刻を分散
tasks = []
for i in range(self.total_users):
# 平均60分(8:00)、標準偏差20分
delay = max(0, np.random.normal(60, 20) * 60)
tasks.append(user_login(i, delay))
start_time = time.time()
await asyncio.gather(*tasks)
total_time = time.time() - start_time
# 結果の分析
successful = sum(1 for r in self.results if r.get('status') == 200)
failed = len(self.results) - successful
avg_response = np.mean([
r['response_time'] for r in self.results
if r.get('status') == 200
])
return {
'total_users': self.total_users,
'successful_logins': successful,
'failed_logins': failed,
'success_rate': successful / self.total_users * 100,
'average_response_time': avg_response,
'total_test_time': total_time,
'requests_per_second': self.total_users / total_time
}
スケーリング対策:
class AutoScalingStrategy:
def __init__(self):
self.metrics_history = []
self.scaling_decisions = []
def analyze_and_recommend(self, current_metrics):
"""メトリクスに基づくスケーリング推奨"""
recommendations = []
# CPU使用率ベース
if current_metrics['cpu_usage'] > 80:
recommendations.append({
'type': 'scale_out',
'reason': 'CPU使用率が80%を超過',
'action': 'インスタンスを2台追加',
'priority': 'high'
})
# レスポンスタイムベース
if current_metrics['p95_response_time'] > 1000: # 1秒
recommendations.append({
'type': 'optimize',
'reason': 'レスポンスタイムが遅い',
'action': 'キャッシュ層の追加',
'priority': 'medium'
})
# エラー率ベース
if current_metrics['error_rate'] > 0.01: # 1%
recommendations.append({
'type': 'investigate',
'reason': 'エラー率が高い',
'action': 'ログ分析とデバッグ',
'priority': 'high'
})
# 予測的スケーリング
if self._predict_traffic_spike(current_metrics['timestamp']):
recommendations.append({
'type': 'pre_scale',
'reason': '過去のパターンから負荷増大を予測',
'action': '30分前にインスタンス追加',
'priority': 'medium'
})
return recommendations
B.4 セキュリティインシデント対応
B.4.1 不正アクセスの検知と対応
症状:異常なログイン試行の増加
リアルタイム検知システム:
class SecurityIncidentDetector:
def __init__(self):
self.thresholds = {
'failed_login_rate': 10, # 5分間で10回
'geo_velocity': 1000, # km/h
'concurrent_sessions': 5, # 同時セッション数
'unusual_hour_login': (0, 5) # 深夜帯
}
async def analyze_login_attempt(self, event):
"""ログイン試行の分析"""
user_id = event['user_id']
# 1. ブルートフォース検知
recent_failures = await self.get_recent_failures(user_id)
if len(recent_failures) >= self.thresholds['failed_login_rate']:
return {
'risk_level': 'critical',
'threat_type': 'brute_force',
'action': 'block_and_notify',
'details': f'{len(recent_failures)}回の失敗'
}
# 2. 地理的異常検知
last_location = await self.get_last_login_location(user_id)
if last_location:
velocity = self.calculate_velocity(
last_location,
event['location'],
event['timestamp'] - last_location['timestamp']
)
if velocity > self.thresholds['geo_velocity']:
return {
'risk_level': 'high',
'threat_type': 'impossible_travel',
'action': 'require_2fa',
'details': f'移動速度: {velocity}km/h'
}
# 3. 同時セッション検知
active_sessions = await self.get_active_sessions(user_id)
if len(active_sessions) >= self.thresholds['concurrent_sessions']:
return {
'risk_level': 'medium',
'threat_type': 'concurrent_sessions',
'action': 'notify_user',
'details': f'{len(active_sessions)}個の同時セッション'
}
return {'risk_level': 'low', 'action': 'allow'}
インシデント対応プレイブック:
incident_response_playbook:
brute_force_attack:
detection:
- threshold: "10 failed attempts in 5 minutes"
- pattern: "Sequential username attempts"
immediate_actions:
- block_ip_address:
duration: "24 hours"
scope: "application_level"
- enable_captcha:
affected_ips: "attacking_ip_range"
- notify_security_team:
priority: "high"
channels: ["slack", "pagerduty"]
investigation:
- check_logs:
timeframe: "last_24_hours"
focus: ["source_ips", "user_agents", "attempted_usernames"]
- analyze_pattern:
type: ["credential_stuffing", "dictionary_attack", "targeted"]
mitigation:
- implement_rate_limiting:
limit: "3 attempts per 5 minutes"
- enforce_account_lockout:
threshold: "5 failed attempts"
duration: "30 minutes"
- consider_ip_reputation:
service: "abuseipdb"
account_takeover:
detection:
- indicators: ["unusual_location", "new_device", "suspicious_activity"]
immediate_actions:
- suspend_account:
grace_period: "0"
- invalidate_all_sessions:
except: "verified_devices"
- force_password_reset:
method: "secure_email_link"
user_communication:
- send_notification:
channels: ["email", "sms", "push"]
template: "security_alert"
- provide_instructions:
content: ["verify_recent_activity", "secure_account", "contact_support"]
B.4.2 データ漏洩の痕跡
症状:異常なデータアクセスパターン
フォレンジック分析ツール:
class AuthForensicsAnalyzer:
def __init__(self, log_sources):
self.log_sources = log_sources
async def analyze_user_activity(self, user_id, timeframe):
"""ユーザーアクティビティの詳細分析"""
# すべてのログソースから情報収集
auth_logs = await self.log_sources['auth'].query(user_id, timeframe)
access_logs = await self.log_sources['access'].query(user_id, timeframe)
audit_logs = await self.log_sources['audit'].query(user_id, timeframe)
# タイムライン構築
timeline = self.build_timeline(auth_logs, access_logs, audit_logs)
# 異常パターンの検出
anomalies = []
# 1. 大量データアクセス
data_access_volume = self.calculate_data_access_volume(access_logs)
if data_access_volume > self.get_user_baseline(user_id) * 10:
anomalies.append({
'type': 'excessive_data_access',
'severity': 'high',
'volume': data_access_volume,
'timeframe': self.identify_peak_period(access_logs)
})
# 2. 異常なアクセスパターン
access_pattern = self.analyze_access_pattern(access_logs)
if access_pattern['is_automated']:
anomalies.append({
'type': 'automated_access',
'severity': 'critical',
'indicators': access_pattern['indicators'],
'confidence': access_pattern['confidence']
})
# 3. 権限昇格の痕跡
privilege_changes = self.detect_privilege_escalation(audit_logs)
if privilege_changes:
anomalies.append({
'type': 'privilege_escalation',
'severity': 'critical',
'changes': privilege_changes
})
return {
'user_id': user_id,
'timeframe': timeframe,
'timeline': timeline,
'anomalies': anomalies,
'risk_score': self.calculate_risk_score(anomalies),
'recommendations': self.generate_recommendations(anomalies)
}
def generate_forensics_report(self, analysis_results):
"""フォレンジックレポートの生成"""
return {
'executive_summary': self.create_executive_summary(analysis_results),
'technical_details': {
'timeline': analysis_results['timeline'],
'anomalies': analysis_results['anomalies'],
'evidence': self.collect_evidence(analysis_results)
},
'impact_assessment': self.assess_impact(analysis_results),
'remediation_steps': self.recommend_remediation(analysis_results),
'legal_considerations': self.identify_legal_requirements(analysis_results)
}
B.5 一般的なエラーメッセージと対処法
B.5.1 エラーメッセージ対応表
エラーメッセージ | 考えられる原因 | 対処法 |
---|---|---|
“Invalid CSRF token” | セッション切れ、Cookie無効 | ページリロード、Cookie設定確認 |
“Token signature verification failed” | 秘密鍵不一致、改ざん | 鍵設定確認、トークン再発行 |
“Rate limit exceeded” | 短時間の多数リクエスト | レート制限値の調整、分散処理 |
“Session expired” | タイムアウト、Redis接続断 | セッション延長、Redis監視 |
“Invalid state parameter” | OAuth state不一致 | セッション設定、CSRF対策確認 |
“Authenticator not found” | WebAuthn登録なし | 登録フロー案内、フォールバック |
B.5.2 デバッグ用ユーティリティ
class AuthDebugger:
"""認証デバッグ用ユーティリティ"""
@staticmethod
def decode_jwt_unsafe(token):
"""JWTをデコード(検証なし、デバッグ用のみ)"""
try:
parts = token.split('.')
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
return {
'header': header,
'payload': payload,
'signature': parts[2],
'expires_at': datetime.fromtimestamp(payload.get('exp', 0)),
'issued_at': datetime.fromtimestamp(payload.get('iat', 0))
}
except Exception as e:
return {'error': str(e)}
@staticmethod
def test_password_encoding(password):
"""パスワードエンコーディングのテスト"""
encodings = {
'utf-8': password.encode('utf-8'),
'latin-1': password.encode('latin-1', errors='ignore'),
'ascii': password.encode('ascii', errors='ignore')
}
results = {}
for encoding, encoded in encodings.items():
results[encoding] = {
'bytes': encoded,
'hex': encoded.hex(),
'length': len(encoded)
}
return results
@staticmethod
def verify_system_time():
"""システム時刻の確認"""
import ntplib
try:
ntp_client = ntplib.NTPClient()
response = ntp_client.request('pool.ntp.org')
local_time = time.time()
ntp_time = response.tx_time
diff = local_time - ntp_time
return {
'local_time': datetime.fromtimestamp(local_time),
'ntp_time': datetime.fromtimestamp(ntp_time),
'difference_seconds': diff,
'synchronized': abs(diff) < 5
}
except Exception as e:
return {'error': str(e)}