sumer5020/laravel-keycloak-guard 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

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

sumer5020/laravel-keycloak-guard

Composer 安装命令:

composer require sumer5020/laravel-keycloak-guard

包简介

A Keycloak Guard for Laravel — robsontenorio/laravel-keycloak-guard fork

README 文档

README

Note: This Version is not production ready yet, still in active development

robsontenorio/laravel-keycloak-guard fork — Full support for Laravel 13, Keycloak 26+, and the Keycloak 26 Organizations feature (--features=organization).

Laravel Keycloak PHP License: MIT

What's New VS the Original Package:

Feature Original This Version
Laravel 13 support
Keycloak 26 JWKS auto-discovery
Automatic key rotation support
Keycloak 26 Organizations
KeycloakOrg facade
keycloak.org middleware
keycloak.org.role middleware
HasOrganizations trait
TokenUser (no-DB mode) Partial
Auth::payload()
Auth::roles()
RealmResource (API Resource)
keycloak:doctor command
ActingAsKeycloak (Testing Trait)
PHP 8.3 constructor promotion

Requirements

  • PHP 8.3+
  • Laravel 12.x / 13.x
  • Keycloak 21+ (Organizations require 26+)

Installation

composer require sumer5020/laravel-keycloak-guard

Publish the config:

php artisan vendor:publish --tag=keycloak-config

Optionally publish migrations (only if using organizations.sync_to_database):

php artisan vendor:publish --tag=keycloak-migrations
php artisan migrate

Health & Diagnostics

The package includes a "Doctor" command to help you troubleshoot connectivity and configuration issues.

php artisan keycloak:doctor

It performs the following checks:

  • Configuration: Verifies that required environment variables are set.
  • Connectivity: Attempts to fetch the OIDC discovery document (.well-known/openid-configuration) from Keycloak.
  • Organizations: Checks if the organization feature is correctly configured.

API Resources

If you need to expose your Keycloak configuration (realm name, base URL, issuer, JWKS URI) to your frontend (e.g., for keycloak-js), you can use the built-in RealmResource.

use KeycloakGuard\Http\Resources\RealmResource;

Route::get('/keycloak/config', function () {
    return new RealmResource([]);
});

This returns a standardized JSON response:

{
  "realm": "my-realm",
  "base_url": "https://keycloak.example.com",
  "jwks_uri": "https://keycloak.example.com/realms/my-realm/protocol/openid-connect/certs",
  "issuer": "https://keycloak.example.com/realms/my-realm"
}

Basic Setup

1. Configure the Guard

config/auth.php:

'guards' => [
    'api' => [
        'driver'   => 'keycloak',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model'  => App\Models\User::class,
    ],
],

2. Environment Variables

# ── Required ────────────────────────────────────────────────
KEYCLOAK_BASE_URL=https://keycloak.example.com
KEYCLOAK_REALM=my-realm
KEYCLOAK_ALLOWED_RESOURCES=my-laravel-api

# ── Key / Token Verification ────────────────────────────────
# Option A: Static public key (from Realm Settings → Keys → RS256 → Public Key)
KEYCLOAK_REALM_PUBLIC_KEY=MIIBIjANBgkq...

# Option B: JWKS auto-discovery (recommended for Keycloak 26+, supports key rotation)
# Leave KEYCLOAK_REALM_PUBLIC_KEY empty — the package constructs the JWKS URI automatically
# Or set it explicitly:
KEYCLOAK_JWKS_URI=https://keycloak.example.com/realms/my-realm/protocol/openid-connect/certs
KEYCLOAK_JWKS_CACHE_TTL=3600

# ── User Matching ────────────────────────────────────────────
KEYCLOAK_TOKEN_PRINCIPAL_ATTRIBUTE=sub        # JWT claim used as identifier
KEYCLOAK_USER_PROVIDER_CREDENTIAL=keycloak_id # DB column to match against

# ── Optional ─────────────────────────────────────────────────
KEYCLOAK_APPEND_DECODED_TOKEN=true            # Attach $user->token (stdClass)
KEYCLOAK_LEEWAY=30                            # Clock skew tolerance (seconds)
KEYCLOAK_LOAD_USER_FROM_DATABASE=true
KEYCLOAK_TOKEN_ENCRYPTION_ALGORITHM=RS256

# ── Organizations (Keycloak 26+) ─────────────────────────────
KEYCLOAK_ORGANIZATIONS_ENABLED=true
KEYCLOAK_ORGANIZATION_HEADER=X-Organization   # Header for selecting active org
KEYCLOAK_ORGANIZATION_REQUIRE=false           # 403 if no org in token?
KEYCLOAK_ORGANIZATION_SYNC_DB=false           # Persist orgs to database?

3. Protect Routes

// routes/api.php

// Basic auth
Route::middleware('auth:api')->group(function () {
    Route::get('/me', fn(Request $r) => $r->user());
});

// Role-based access
Route::middleware(['auth:api', 'keycloak.role:admin'])->group(function () {
    Route::get('/admin', AdminController::class);
});

// Organization-scoped routes (Keycloak 26+)
Route::middleware(['auth:api', 'keycloak.org'])->group(function () {
    Route::apiResource('projects', ProjectController::class);

    // Org admin only
    Route::middleware('keycloak.org.role:admin')->group(function () {
        Route::apiResource('members', MemberController::class);
    });
});

Keycloak 26 Organizations

How It Works

When Keycloak is started with --features=organization, it injects an organization claim into the JWT:

{
  "sub": "f7a8b9c0-...",
  "preferred_username": "john@acme.com",
  "organization": {
    "acme-corp": {
      "id": "3fa85f64-...",
      "name": "Acme Corporation",
      "roles": ["admin", "member"]
    }
  }
}

For users in multiple organizations, the client sends an X-Organization header to select the active one.

The KeycloakOrg Facade

use KeycloakGuard\Facades\KeycloakOrg;

// Get the active organization for this request
$org = KeycloakOrg::current();
// => ['alias' => 'acme-corp', 'id' => '3fa85f64-...', 'name' => 'Acme Corporation', 'roles' => ['admin']]

// Get all organizations the user belongs to (from the token)
$orgs = KeycloakOrg::all();

// Check membership
KeycloakOrg::belongsTo('acme-corp');        // bool
KeycloakOrg::hasOrganizations();             // bool

// Role checks within the active organization
KeycloakOrg::hasRole('admin');               // bool
KeycloakOrg::hasRole('admin', 'acme-corp'); // bool (explicit org)
KeycloakOrg::hasAnyRole(['admin', 'billing-manager']); // bool

// Sync to DB (requires sync_to_database = true)
KeycloakOrg::syncToDatabase(auth()->user());

HasOrganizations Trait

Add to your User model for database relationships and convenient helpers:

use KeycloakGuard\Traits\HasOrganizations;

class User extends Authenticatable
{
    use HasOrganizations;
}

Then use:

// From JWT (no DB query)
$user->currentOrganization();        // array|null
$user->hasOrgRole('admin');          // bool
$user->belongsToOrg('acme-corp');    // bool

// From database (requires sync_to_database)
$user->organizations()->get();
$user->organizations()->where('alias', 'acme-corp')->first();

Using Organization Context in Controllers

class ProjectController extends Controller
{
    public function index(): JsonResponse
    {
        // Array from JWT — no DB query
        $org = KeycloakOrg::current();

        // Eloquent model — only if sync_to_database = true
        $orgModel = app('current_organization_model');

        return response()->json(
            Project::where('organization_id', $orgModel->id)->paginate()
        );
    }

    public function store(StoreProjectRequest $request): JsonResponse
    {
        $org = app('current_organization_model');

        $project = Project::create([
            ...$request->validated(),
            'organization_id' => $org->id,
        ]);

        return response()->json($project, 201);
    }
}

Auth Guard Methods

use Illuminate\Support\Facades\Auth;

// Standard Laravel
Auth::user();           // Authenticatable|null
Auth::check();          // bool
Auth::id();             // mixed

// Keycloak-specific
Auth::token();          // string|null  — full decoded token as JSON
Auth::payload();        // stdClass|null — decoded token object
Auth::roles();          // array — all realm + resource roles (with inheritance)
Auth::roles('my-api'); // array         — resource-specific roles (includes realm roles)
Auth::hasRole('admin'); // bool
Auth::hasRole('editor', 'my-api'); // bool — resource-scoped

// Organizations
Auth::organizations();  // OrganizationService

Middleware Reference

Middleware Description Example
keycloak.role:admin Requires realm or any resource role middleware('keycloak.role:admin,editor')
keycloak.role:editor|my-api Requires role in specific resource middleware('keycloak.role:editor|my-api')
keycloak.org Resolves active org from JWT + header middleware('keycloak.org')
keycloak.org.role:admin Requires org-level role middleware('keycloak.org.role:admin')

Note: The keycloak.role middleware supports role inheritance. If a user has a realm-level role (e.g., admin), they will pass checks for any resource-specific role requirements.

No-Database Mode

If your API doesn't have a users table, set:

KEYCLOAK_LOAD_USER_FROM_DATABASE=false
KEYCLOAK_APPEND_DECODED_TOKEN=true

Auth::user() returns a TokenUser built from JWT claims:

$user = Auth::user();
$user->sub;                 // UUID
$user->preferred_username;  // username
$user->email;               // email
$user->token;               // full stdClass of JWT claims

Custom User Provider

// app/Providers/CustomUserProvider.php
class CustomUserProvider implements UserProvider
{
    public function customRetrieveUser(stdClass $token, array $credentials): ?User
    {
        return User::updateOrCreate(
            ['keycloak_id' => $token->sub],
            [
                'name'  => $token->name ?? $token->preferred_username,
                'email' => $token->email,
            ]
        );
    }
    // ... other required methods
}
KEYCLOAK_USER_PROVIDER_CUSTOM_RETRIEVE_METHOD=customRetrieveUser

Keycloak 26 — Admin Configuration Checklist

In your Keycloak 26 admin console:

Realm Settings → Keys → RS256: Copy the public key for KEYCLOAK_REALM_PUBLIC_KEY, or leave blank to use JWKS auto-discovery.

Client Settings (for your Laravel API client):

  • Client protocol: openid-connect
  • Access type: confidential
  • Standard flow: OFF (API only)
  • Direct access grants: OFF
  • Service accounts enabled: ON (for M2M)

Organizations (Keycloak 26+):

  • Realm Settings → General → Enable Organizations: ON
  • OR start Keycloak with --features=organization
  • Add the organization mapper to your client's token scope

Token Mappers — ensure your client has:

  • organization claim mapper (type: Organization Membership)
  • realm roles mapper
  • client roles mapper

Testing

The package provides a powerful ActingAsKeycloak trait to mock authenticated states in your feature tests without needing a live Keycloak server.

namespace Tests\Feature;

use KeycloakGuard\Testing\ActingAsKeycloak;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use ActingAsKeycloak;

    public function test_api_is_protected(): void
    {
        // Simple authentication
        $this->withKeycloakToken(['name' => 'John Doe'])
             ->getJson('/api/me')
             ->assertOk();
             
        // Authentication with specific roles
        $this->withKeycloakToken([
            'realm_access' => ['roles' => ['admin']]
        ])->getJson('/api/admin')->assertOk();
    }

    public function test_organization_access(): void
    {
        // Authenticate with organization membership
        $this->withKeycloakOrganization([
            'acme' => ['id' => 'org-1', 'name' => 'Acme Corp', 'roles' => ['member']]
        ])
        ->withOrganization('acme') // Select active org via header
        ->getJson('/api/projects')
        ->assertOk();
    }
}

License

MIT

sumer5020/laravel-keycloak-guard 适用场景与选型建议

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

它主要适用于以下技术方向: 「oauth2」 「auth」 「laravel」 「saas」 「jwt」 「guard」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

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

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

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-03-23