承接 andydefer/laravel-nemesis 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

andydefer/laravel-nemesis

Composer 安装命令:

composer require andydefer/laravel-nemesis

包简介

Nemesis: Multi-model token-based authentication for Laravel

README 文档

README

PHP Version Laravel Version License Tests

Nemesis est un package Laravel complet pour l’authentification par tokens multi-modèles utilisant le système de Directives (CLI flexible). Contrairement à Sanctum ou Passport, Nemesis permet à n’importe quel modèle Eloquent (User, CheckPoint, ApiClient, Admin, etc.) de générer, valider et gérer ses propres tokens d’API avec une sécurité renforcée : expiration, permissions (abilities), restrictions CORS par origine, métadonnées, soft delete pour révocation, et nettoyage automatique.

📦 Installation

composer require andydefer/laravel-nemesis

Vérification de l'installation

# Lister les directives disponibles
./vendor/bin/directive --list

# Installer le package
./vendor/bin/directive install-nemesis --force

Publication manuelle des ressources (alternative)

# Configuration
php artisan vendor:publish --tag=nemesis-config

# Migrations
php artisan vendor:publish --tag=nemesis-migrations

# Exécuter les migrations
php artisan migrate

🚀 Démarrage rapide

1. Ajouter l’interface à vos modèles

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use AndyDefer\Nemesis\Contracts\MustNemesis;
use AndyDefer\DomainStructures\Abstracts\AbstractData;

class User extends Model implements MustNemesis
{
    /**
     * Définir ce qui est exposé par l'API.
     * Cette méthode est OBLIGATOIRE (imposée par l'interface).
     */
    public function nemesisFormat(): AbstractData
    {
        return new UserData(
            id: $this->id,
            name: $this->name,
            email: $this->email,
            createdAt: $this->created_at,
        );
    }
}

class CheckPoint extends Model implements MustNemesis
{
    /**
     * Format différent pour les points de contrôle.
     */
    public function nemesisFormat(): AbstractData
    {
        return new CheckPointData(
            id: $this->id,
            name: $this->name,
            location: $this->location,
            status: $this->is_active ? 'active' : 'inactive',
            lastSeen: $this->last_ping_at,
        );
    }
}

2. Créer un token

use AndyDefer\Nemesis\Records\NemesisTokenRecord;

$user = User::find(1);

$record = NemesisTokenRecord::from([
    'name' => 'Application Mobile',
    'source' => 'mobile',
    'abilities' => ['scan_ticket', 'view_stats'],
    'metadata' => ['app_version' => '2.1.0'],
]);

[$token, $plainToken] = $nemesisService->createWithPlainToken($record, $user);

// Afficher le token une seule fois
echo $plainToken; // stocker en clair côté client

3. Protéger une route

// Dans routes/api.php
use AndyDefer\Nemesis\Facades\NemesisHelper;

Route::middleware(['nemesis.token'])->group(function () {
    Route::get('/profile', function () {
        // Version formatée via le Facade
        return response()->json(NemesisHelper::getCurrentAuthenticatableFormat());
    });
});

// Avec vérification d’une ability
Route::post('/scan', function () {
    // ...
})->middleware('nemesis.token:scan_ticket');

4. Utiliser le token

GET /api/profile
Authorization: Bearer <token>

5. Utilisation du Facade NemesisHelper

use AndyDefer\Nemesis\Facades\NemesisHelper;

// Récupérer le token actuel
$token = NemesisHelper::getCurrentToken();

// Récupérer le modèle authentifié (User, CheckPoint, etc.)
$authenticatable = NemesisHelper::getCurrentAuthenticatable();

// Récupérer la version formatée (recommandée pour les APIs)
$formatted = NemesisHelper::getCurrentAuthenticatableFormat();

// Vérifier si authentifié
if (NemesisHelper::hasCurrentAuthenticatable()) {
    // ...
}

// Vérifier si un token est présent
if (NemesisHelper::hasCurrentToken()) {
    // ...
}

🎯 Révocation granulaire des tokens

Nemesis permet de révoquer sélectivement les tokens par source, nom ou critères personnalisés via le service NemesisService.

Méthodes de révocation

Méthode Description Valeur de retour
revokeTokensBySource(Model $tokenable, string $source, bool $force = false) Révoque tous les tokens d'une source spécifique int
revokeTokensByName(Model $tokenable, string $name, bool $force = false) Révoque tous les tokens avec un nom spécifique int
revokeTokensBySourceAndName(Model $tokenable, string $source, string $name, bool $force = false) Révoque les tokens correspondant à source ET nom int
revokeAllTokensExceptSource(Model $tokenable, string $source, bool $force = false) Garde les tokens d'une source, révoque tous les autres int
deleteAllTokens(Model $tokenable, bool $force = false) Supprime tous les tokens d'un modèle int
deleteBulk(NemesisTokenFilterRecord $filters) Supprime en masse selon filtres int
forceDeleteBulk(NemesisTokenFilterRecord $filters) Suppression définitive selon filtres int

Filtres disponibles

$filters = new NemesisTokenFilterRecord(
    tokenable_type: 'App\\Models\\User',
    tokenable_id: 1,
    source: 'web',
    name: 'session_token',
    is_expired: true,
    is_revoked: false,
    created_before: DateTimeVO::from(now()->subDays(30)->toIso8601String()),
);

Cas d'usage concrets

1. Déconnexion de tous les navigateurs (garder l'app mobile active)

$nemesisService->revokeTokensBySource($user, 'web');

2. Révocation sélective par type de token

$nemesisService->revokeTokensBySourceAndName($user, 'web', 'web_session');
$nemesisService->revokeTokensByName($user, 'admin_token');

3. Garder un type de token actif

$nemesisService->revokeAllTokensExceptSource($user, 'api');

4. Suppression définitive (force delete)

$nemesisService->revokeTokensBySource($user, 'web', force: true);
$nemesisService->deleteAllTokens($user, force: true);

5. Nettoyage par filtres avancés

$filter = new NemesisTokenFilterRecord(
    created_before: DateTimeVO::from(now()->subDays(30)->toIso8601String()),
);
$nemesisService->forceDeleteBulk($filter);

🛡️ Sécurité multi-origines (CORS)

Nemesis permet de restreindre un token à des origines spécifiques.

$nemesisService->addAllowedOrigin($token, 'https://monapp.com');
$nemesisService->addAllowedOrigin($token, 'https://*.example.com'); // wildcard

// Vérification
if ($nemesisService->canUseFromOrigin($token, 'https://monapp.com')) {
    // origine autorisée
}

🔑 Système d’abilities (permissions fines)

Chaque token peut avoir une liste d’abilities.

// Création avec abilities
$record = NemesisTokenRecord::from([
    'name' => 'Scanner Billeterie',
    'source' => 'kiosk',
    'abilities' => ['scan_ticket', 'validate_entry'],
]);

// Vérifier une ability
if ($nemesisService->can($token, 'scan_ticket')) {
    // autorisé
}

Utilisation en middleware :

Route::post('/validate', fn() => ...)
    ->middleware('nemesis.token:validate_entry');

📦 Métadonnées enrichies

Stockez des informations contextuelles avec validation automatique (taille max 64KB, profondeur max 5, max 100 clés).

$record = NemesisTokenRecord::from([
    'name' => 'API Session',
    'metadata' => [
        'device' => 'iPhone 15',
        'os' => 'iOS 17',
        'location' => 'Paris',
        'preferences' => ['lang' => 'fr']
    ],
]);

// Modifier après création
$nemesisService->setMetadata($token, 'last_login_ip', '192.168.1.1');
$ip = $nemesisService->getMetadata($token, 'last_login_ip');
$nemesisService->mergeMetadata($token, ['new_key' => 'value']);

🧹 Nettoyage automatique des tokens expirés

Configuration dans config/nemesis.php :

'cleanup' => [
    'auto_cleanup' => true,       // nettoyage auto par schedule
    'frequency' => 60,            // toutes les heures
    'keep_expired_for_days' => 30, // garder 30 jours pour audit
],

Directive manuelle :

./vendor/bin/directive clean-tokens --force
./vendor/bin/directive clean-tokens --days=15
./vendor/bin/directive clean-tokens --keep-expired
./vendor/bin/directive nemesis-clean --force  # alias

📋 Directives disponibles

Commande Alias Description
install-nemesis nemesis-install, setup-nemesis Installation du package
list-tokens tokens-list, nemesis-tokens Liste tous les tokens
clean-tokens tokens-clean, token-clean, clean-expired Nettoie les tokens expirés
nemesis-clean token-clean, tokens-clean Alias de clean-tokens
# Lister les directives Nemesis
./vendor/bin/directive --list | grep -E "nemesis|token|clean"

# Aide sur une directive
./vendor/bin/directive help clean-tokens

🔗 Scénario concret : Billeterie avec User et CheckPoint

Modèles

// User (client billetterie)
class User extends Model implements MustNemesis
{
    public function nemesisFormat(): UserData
    {
        return new UserData(
            id: $this->id,
            name: $this->name,
            email: $this->email,
        );
    }
}

// CheckPoint (point de contrôle physique)
class CheckPoint extends Model implements MustNemesis
{
    public function nemesisFormat(): CheckPointData
    {
        return new CheckPointData(
            id: $this->id,
            name: $this->name,
            location: $this->location,
            status: $this->is_active ? 'active' : 'inactive',
        );
    }
}

Création des tokens

// Pour un utilisateur (application mobile)
$record = NemesisTokenRecord::from([
    'name' => 'App Mobile Client',
    'source' => 'mobile',
    'abilities' => ['buy_ticket', 'view_tickets'],
]);
[$token, $plainToken] = $nemesisService->createWithPlainToken($record, $user);

// Pour un point de contrôle (kiosque)
$record = NemesisTokenRecord::from([
    'name' => 'Scanner Portique',
    'source' => 'kiosk',
    'abilities' => ['scan_ticket', 'validate_entry', 'reject_entry'],
    'metadata' => ['hardware_id' => 'SCAN-01', 'location' => 'Entrée A'],
]);
[$token, $plainToken] = $nemesisService->createWithPlainToken($record, $checkpoint);

Routes protégées

Route::middleware('nemesis.token:buy_ticket')->post('/tickets', [TicketController::class, 'buy']);
Route::middleware('nemesis.token:scan_ticket')->post('/scan', [ScanController::class, 'validate']);

Dans ScanController

use AndyDefer\Nemesis\Facades\NemesisHelper;

public function validate(Request $request)
{
    $checkpoint = NemesisHelper::getCurrentAuthenticatable(); // instance de CheckPoint
    $token = NemesisHelper::getCurrentToken();

    if (!$token || !$nemesisService->can($token, 'validate_entry')) {
        return response()->json(['error' => 'Permission refusée'], 403);
    }

    // scanner le billet...
    return response()->json([
        'status' => 'entrée validée',
        'checkpoint' => NemesisHelper::getCurrentAuthenticatableFormat()
    ]);
}

Révocation depuis le point de contrôle

use AndyDefer\Nemesis\Facades\NemesisHelper;

public function logoutCheckPoint()
{
    $token = NemesisHelper::getCurrentToken();
    
    if ($token && $nemesisService->revoke($token)) {
        return response()->json(['message' => 'Token révoqué avec succès']);
    }
    
    return response()->json(['error' => 'Aucun token actif trouvé'], 404);
}

📊 API complète du service

NemesisService

Méthode Description Retour
create(NemesisTokenRecord $record, Model $tokenable) Crée un token avec hash existant NemesisToken
createWithPlainToken(NemesisTokenRecord $record, Model $tokenable) Génère un nouveau token array[NemesisToken, string]
findByHash(string $tokenHash) Trouve un token par son hash ?NemesisToken
updateLastUsed(NemesisToken $token) Met à jour last_used_at NemesisToken
revoke(NemesisToken $token) Soft delete du token bool
restoreToken(NemesisToken $token) Restaure un token soft-deleted bool
forceExpire(NemesisToken $token) Expire immédiatement le token NemesisToken
forceDelete(NemesisToken $token) Suppression définitive bool
can(NemesisToken $token, string $ability) Vérifie une ability bool
canAll(NemesisToken $token, array $abilities) Vérifie toutes les abilities bool
canUseFromOrigin(NemesisToken $token, ?string $origin) Vérifie l'origine CORS bool
deleteBulk(NemesisTokenFilterRecord $filters) Soft delete en masse int
forceDeleteBulk(NemesisTokenFilterRecord $filters) Suppression définitive en masse int
count(NemesisTokenFilterRecord $filters) Compte les tokens int
exists(NemesisTokenFilterRecord $filters) Vérifie l'existence bool
findByFilters(NemesisTokenFilterRecord $filters, ?int $limit = null, ?string $sortBy = null, array $columns = ['*']) Recherche avancée Collection
getMetadata(NemesisToken $token, string $key, mixed $default = null) Récupère une métadonnée mixed
setMetadata(NemesisToken $token, string $key, mixed $value) Définit une métadonnée NemesisToken
mergeMetadata(NemesisToken $token, array $metadata) Fusionne des métadonnées NemesisToken
clearMetadata(NemesisToken $token) Supprime toutes les métadonnées NemesisToken
addAllowedOrigin(NemesisToken $token, string $origin) Ajoute une origine CORS NemesisToken
removeAllowedOrigin(NemesisToken $token, string $origin) Supprime une origine CORS NemesisToken

NemesisHelper (Facade)

Méthode Description Retour
getCurrentToken() Récupère le token actuel ?NemesisTokenRecord
getCurrentAuthenticatable() Récupère le modèle authentifié ?Model
getCurrentAuthenticatableFormat() Récupère la version formatée ?AbstractData
hasCurrentToken() Vérifie si un token est présent bool
hasCurrentAuthenticatable() Vérifie si authentifié bool

⚙️ Configuration (config/nemesis.php)

return [
    // Génération des tokens
    'token_length' => 64,
    'hash_algorithm' => 'sha256',
    'expiration' => 60, // null = jamais, sinon minutes

    // Middleware
    'middleware' => [
        'parameter_name' => 'nemesis_auth',
        'token_header' => 'Authorization',
        'security_headers' => true,
        'validate_origin' => true,
    ],

    // CORS
    'cors' => [
        'allow_credentials' => true,
        'max_age' => 86400,
        'expose_token_info' => false,
    ],

    // Nettoyage
    'cleanup' => [
        'auto_cleanup' => true,
        'frequency' => 60,
        'keep_expired_for_days' => 30,
    ],
];

📁 Structure des migrations

CREATE TABLE nemesis_tokens (
    id BIGINT PRIMARY KEY,
    token_hash VARCHAR(255) UNIQUE NOT NULL,
    tokenable_type VARCHAR(255) NOT NULL,
    tokenable_id BIGINT NOT NULL,
    name VARCHAR(255) NULL,
    source VARCHAR(255) NULL,
    abilities TEXT NULL,           -- JSON
    metadata TEXT NULL,            -- JSON
    allowed_origins TEXT NULL,     -- JSON
    last_used_at TIMESTAMP NULL,
    expires_at TIMESTAMP NULL,
    deleted_at TIMESTAMP NULL,     -- Soft delete
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL,
    
    INDEX idx_tokenable (tokenable_type, tokenable_id),
    INDEX idx_expires_at (expires_at),
    INDEX idx_source (source),
    INDEX idx_last_used (last_used_at),
    UNIQUE INDEX idx_token_hash (token_hash)
);

🧠 Ce que Nemesis résout concrètement

Problème Solution Nemesis
Plusieurs modèles doivent s’authentifier (User, CheckPoint) Polymorphisme tokenable
Contrôle total des données exposées via API Méthode obligatoire nemesisFormat() retournant AbstractData
Déconnexion sélective (web vs mobile) revokeTokensBySource()
Révocation granulaire par type de token revokeTokensByName()
Nettoyage des tokens inactifs deleteBulk() avec filtres
Garder certains tokens actifs revokeAllTokensExceptSource()
Un token ne doit servir que pour certaines origines allowed_origins + validation middleware
Une application mobile a moins de droits qu’un admin abilities (ex: scan_ticket vs delete_user)
Besoin de tracer le contexte (IP, device, version) metadata validé et nettoyé
Révocation sans perte d’audit softDeletes
Nettoyage des tokens obsolètes Commande schedule + auto_cleanup
Un token peut expirer après X minutes expires_at + isExpired()

🔄 Comparaison rapide avec Laravel Sanctum

Fonctionnalité Sanctum Nemesis
Multi-modèles (User + CheckPoint) ❌ (seulement User) ✅ (tout modèle)
CLI avec Directives (pas Artisan)
Contrôle explicite de l'exposition des données ✅ (méthode obligatoire)
Révocation granulaire par source/nom
Révocation par filtres avancés
Restrictions CORS par token ❌ (globale) ✅ (par token)
Métadonnées enrichies ✅ (validation stricte)
Soft delete des tokens
Abilities sans user
Nettoyage auto configurable
Tests en environnement isolé ✅ (DirectiveTestingService)

🧪 Tests

# Exécuter tous les tests
./vendor/bin/phpunit

# Exécuter les tests d'une directive
./vendor/bin/phpunit --filter CleanTokensDirectiveTest

# Exécuter les tests en mode debug
./vendor/bin/phpunit --debug --filter Unit

🤝 Contribution

  1. Fork + branche feature/ma-fonctionnalité
  2. composer test (246 tests doivent passer)
  3. Pull request vers main

📄 Licence

MIT © andydefer


---

andydefer/laravel-nemesis 适用场景与选型建议

andydefer/laravel-nemesis 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 26 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 25 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 andydefer/laravel-nemesis 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 andydefer/laravel-nemesis 我们能提供哪些服务?
定制开发 / 二次开发

基于 andydefer/laravel-nemesis 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 26
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 18
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-25