🛑 第十二章:Anti-Cheat 防作弊機制:位置検証と対策
前章では、万人同時打刻に耐えるFastAPI超高速バックエンドを構築しました。 しかし、システムが速くてもデータが偽物なら、そのシステムは無価値です。
勤怠システムの最大の弱点は「代理打刻」と「不在場打刻」です。 LIFFとLineのJWT Tokenで「代理打刻」は解決済み(自分のLineアカウントでログイン必須)。 今回は「不在場打刻」を解決します。 例えば、従業員が朝8:55に自宅で朝食を食べながら、Line打刻ページで「出勤打刻」を押したとします。システムは正しいJWTを受け取り、データベースに記録します。 月末、社長はこの従業員が毎日遅刻していることに気づきますが、システムは「全勤」と表示!間違いなくシステムは返品されます!
この問題を解決するため、本章では最高級SaaS製品レベルの**「Anti-Cheat(防作弊)エンジン」**を構築します。 フロントエンドのGeolocation(地理的位置情報)とバックエンドのHaversine公式(地球曲率距離演算法)を組み合わせ、ハッカーでも突破困難な防護ネットを作ります。
🌍 実戦1:フロントエンドでユーザーの「GPS緯度経度」を取得
防作弊の第一歩は、従業員が打刻ボタンを押した「瞬間」に、地球上の正確な座標を把握することです。
現代のHTML5は強力なnavigator.geolocation APIを提供しています。
💡 Vibe Prompt 実戦1:GPS許可の優雅な取得と待機
多くのエンジニアはGPS取得コードを乱暴に書き、ユーザーが許可を拒否するとページがクラッシュします。 Vibe Codingでは、AIに非同期待機とエラー処理の優れた体験を教えます。
[!IMPORTANT] 以下のPromptをAIに送信してください(Reactフロントエンド向け):
私の打刻ページで、ユーザーが「打刻」ボタンを押した時、まずそのスマートフォンの現在のGPS緯度経度を取得する必要があります。async関数getCurrentLocation()を作成してください。要件:1. HTML5 navigator.geolocation.getCurrentPositionを使用2. コールバックベースの古いAPIをPromiseでラップし、await可能な現代的な書き方に3. 高精度モード(enableHighAccuracy: true)を設定し、タイムアウトを5秒に4. 成功時はlatとlngを含むオブジェクトを返す5. ユーザーが許可を拒否またはタイムアウト時は明確な日本語エラーメッセージを投げる(例:「打刻にはスマートフォンの位置情報サービスを有効にしてください」)
🤖 AIの高度なPromiseラッピング術:
// geolocation.js
export const getCurrentLocation = () => {
// 古いコールバック構文を現代的なPromiseでラップし、await可能に!
return new Promise((resolve, reject) => {
// ユーザーのブラウザが位置情報をサポートしているか確認(超旧式スマホ対策)
if (!navigator.geolocation) {
reject(new Error("お使いのブラウザまたはデバイスは位置情報機能をサポートしていません。"));
return;
}
// 位置情報取得開始!
navigator.geolocation.getCurrentPosition(
// 成功時のコールバック
(position) => {
resolve({
lat: position.coords.latitude,
lng: position.coords.longitude,
// accuracyは位置情報の誤差範囲(メートル)、後ほど防作弊で使用!
accuracy: position.coords.accuracy
});
},
// 失敗時のコールバック
(error) => {
// AIがエラーコードを人間が理解できる日本語に翻訳
switch (error.code) {
case error.PERMISSION_DENIED:
reject(new Error("打刻失敗:位置情報の許可を拒否されました。システム設定でウェブの位置情報権限を有効にしてください。"));
break;
case error.POSITION_UNAVAILABLE:
reject(new Error("打刻失敗:現在GPS信号を取得できません。室外または窓際で再度お試しください。"));
break;
case error.TIMEOUT:
reject(new Error("打刻失敗:位置情報取得がタイムアウトしました(5秒以上)。ネットワーク接続を確認してください。"));
break;
default:
reject(new Error("打刻失敗:不明な位置情報エラーが発生しました。"));
break;
}
},
// 第三引数:設定オプション
{
enableHighAccuracy: true, // スマートフォンのGPSチップを強制起動(高精度だが電池消費大)
timeout: 5000, // 最大5秒待機(従業員がシステムダウンと誤解しないため)
maximumAge: 0 // キャッシュされた古い位置情報を拒否し、最新位置を強制取得!
}
);
});
};
📡 実戦2:バックエンドで座標受信と「ハバーサイン公式(Haversine)」
フロントエンドがAPIに座標を付加してFastAPIバックエンドに送信します。
フロントエンドから{"lat": 35.6895, "lng": 139.6917}(東京タワー)が送信されたと仮定します。
バックエンドデータベースの「会社打刻中心点」も{"lat": 35.6894, "lng": 139.6916}です。
これらの座標は近そうですが、実際の距離は何メートルでしょうか? 緯度差と経度差を単純に引き算して三平方の定理で計算してはいけません!なぜなら地球は丸いからです!極地に近づくほど経緯度間の物理的距離は短くなります。 実際のメートル数を計算するには、地図学で最も有名な数学公式**Haversine Formula(ハバーサイン公式)**が必要です。
💡 Vibe Prompt 実戦2:AIに曲率演算法を書かせる
このような複雑な三角関数を自力で書くのは間違いの元です。AIに完璧なPythonモジュールを書かせましょう!
[!IMPORTANT] 以下のPromptをAIに送信してください:
私のFastAPIバックエンドで、従業員の打刻緯度経度が会社設定の中心点からどれだけ離れているか判定する必要があります。Python関数calculate_distance_meters(lat1, lon1, lat2, lon2)を作成してください。要件:1. 有名なHaversine公式を使用(地球の曲率を考慮)2. 地球半径は6371kmに設定3. mathモジュールの三角関数を使用(radians, sin, cos, atan2, sqrt)4. 最終的に整数(メートル)を返すクリーンなコードと日本語コメントを付けてください。
🤖 AIの学術レベルの数学演算法:
import math
def calculate_distance_meters(lat1: float, lon1: float, lat2: float, lon2: float) -> int:
"""
Haversine公式を使用して地球上の2点間の実際の直線距離(メートル)を計算。
この演算法は地球の曲面特性を完璧に処理し、業界標準のGPS距離計算方法です!
"""
# 地球平均半径(km)
R = 6371.0
# 度数から三角関数に必要なラジアン(radians)に変換
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
# 経緯度の差を計算
dlon = lon2_rad - lon1_rad
dlat = lat2_rad - lat1_rad
# ハバーサイン公式の魔法の数学演算
a = math.sin(dlat / 2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
# 距離を計算(km)
distance_km = R * c
# kmをメートルに変換し、整数に丸める
distance_meters = int(distance_km * 1000)
return distance_meters
# 【テスト例】:
# 会社が東京タワー(35.658584, 139.7454316)
# 従業員が新宿駅(35.689634, 139.700414)
# print(calculate_distance_meters(35.658584, 139.7454316, 35.689634, 139.700414))
# 出力:3888(メートル)→ システム判定:距離が遠すぎるため打刻失敗!
🚫 実戦3:Anti-Cheat検証ゲートの構築(Validation Logic)
物差し(Haversine)を手に入れたので、打刻APIにゲートを設置します。 従業員の距離が設定した「許容半径(Radius)」を超えた場合、容赦なく拒否します!
💡 Vibe Prompt 実戦3:FastAPIルートと距離検証の統合
[!IMPORTANT] 以下のPromptをAIに送信してください:
私のFastAPIに/api/punchルートがあります。フロントエンドはactionに加え、latとlngも送信します。PunchRequestのPydanticモデルを更新してください。そしてルートにAnti-Cheatロジックを追加:1. 会社の緯度経度をCOMPANY_LAT = 35.6895、COMPANY_LNG = 139.6917と仮定2. 許容打刻半径をALLOWED_RADIUS = 100(メートル)に設定3. 先程作成したcalculate_distance_metersを呼び出し距離を計算4. 距離が100メートルを超える場合、HTTPException 403(Forbidden)を投げ、「打刻失敗!現在の位置は会社から[X]メートル離れています。会社の範囲内で再度お試しください。」と返す統合後の完全なコードを提供してください。
🤖 AIの冷酷な防護ゲート:
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
# 会社本社の座標(実際の環境ではデータベースに保存)
COMPANY_LAT = 35.6895
COMPANY_LNG = 139.6917
ALLOWED_RADIUS = 100 # 100メートル以内の誤差を許容
class PunchRequest(BaseModel):
action: str
lat: float # 追加:フロントエンドからの緯度
lng: float # 追加:フロントエンドからの経度
@router.post("/api/punch")
async def handle_punch(request_data: PunchRequest, current_user: dict = Depends(verify_line_token)):
# 🛑 Anti-Cheat 防護層1:距離検証
distance = calculate_distance_meters(
COMPANY_LAT, COMPANY_LNG,
request_data.lat, request_data.lng
)
print(f"📡 距離検出:従業員 {current_user['name']} は本社から {distance} メートル離れています。")
# 従業員が100メートル以上離れていた場合、直ちにゲートを閉じる!
if distance > ALLOWED_RADIUS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"打刻失敗!打刻可能範囲まであと {distance - ALLOWED_RADIUS} メートルです。会社に近づいて再度お試しください!"
)
# 通過した場合のみ、前章のデータ���ース書き込みロジックを実行
# await record_punch_to_db(...)
return {"status": "success", "message": "完璧な打刻!成功しました。"}
😈 実戦4:ハッカーの逆襲 —— フェイクGPS対策(Fake Location)
社長は満足し、システムが稼働しました。 1週間後、社長はあなたを呼び出します:「王さんは今日出勤していないのに、打刻が成功しています!どういうことですか?」
ログを調べると、王さんがサーバーに送信したlatとlngは会社の座標と完全一致し、小数点以下6桁まで同じでした!
王さんはAndroidの**「Fake GPS(仮想位置)」**アプリをダウンロードし、スマートフォンの位置情報を会社に偽装していたのです!
これが上級バックエンドエンジニアが直面する魔高一丈です。
仮想位置はOSレベルの欺瞞で、ウェブのnavigator.geolocationでは検出できません。
しかし... 狐は必ず尾を出します!
💡 Vibe Prompt 実戦4:Fake GPSを検出するAIロジックの構築
仮想位置の最大の弱点は「完璧すぎる」ことです。
実際のGPS信号は建物の遮蔽や大気の屈折により、精度(Accuracy)は通常15~60メートルで変動します。静止していても、緯度経度の小数点以下は常に微妙に変化します。
一方、安価なFake GPSアプリは、精度0または極端に不自然な固定値を返し、緯度経度は常に同じ数字です!
AIに高度な特徴検出システムを書かせましょう!
[!IMPORTANT] 以下のPromptをAIに送信してください:
従業員がFake GPS(仮想位置)を使用して私の打刻システムを欺いています。フロントエンドはlatとlngに加え、accuracy(位置精度、メートル)も送信します。FastAPIルートに高度なFake GPS防御ロジック(ヒューリスティック検出)を追加してください。検出条件:1. accuracyが5(メートル)未満:人間のスマートフォンGPSで5メートル以内の精度は稀。ソフトウェアで固定された完璧な座標と判定し拒否2. accuracyが1000(メートル)以上:地下室など信号が極端に悪く、位置が数kmも漂っている可能性があるため信頼不可。室外で再打刻を要求3. PunchRequestにaccuracyを追加し、ルートの最初にこれらの防御チェックを挿入
🤖 AIの究極Anti-Cheatレーダー:
class PunchRequest(BaseModel):
action: str
lat: float
lng: float
accuracy: float # 追加:位置��度(フロントエンドのposition.coords.accuracy)
@router.post("/api/punch")
async def handle_punch(request_data: PunchRequest, current_user: dict = Depends(verify_line_token)):
# 🛑 Anti-Cheat 最終防護:仮想位置特徴分析
# 特徴1:精度が異常に完璧(Fake GPSは通常極小の数値を返す)
if request_data.accuracy < 5.0:
print(f"⚠️ [防作弊警告] {current_user['name']} がFake GPS使用の疑い、精度が異常に高い {request_data.accuracy}m!")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="異常な位置情報信号を検出しました。すべての仮想位置ソフトウェアやVPNを無効にしてください。"
)
# 特徴2:信号が極端に弱い(位置が漂いすぎて信頼できない)
if request_data.accuracy > 500.0:
print(f"⚠️ [