🦀 Rust Axum × Keycloak:30 行代码拿下 OIDC 登录 + 角色权限

Photos provided by Unsplash OR Pexels

Rust Axum 框架中生产级 Keycloak OIDC 集成最佳实践

引言背景

Keycloak 是领先的开源身份与访问管理解决方案,完全支持 OpenID Connect (OIDC),作为 2026 年 1 月的最新版本(Keycloak 26.x 系列),它提供自动 Discovery、PKCE、Nonce 等现代安全特性。

在前文 OIDC 通用集成基础上,本节专注 Keycloak 专用配置,实现:

  • 使用 Keycloak 作为 OIDC Provider
  • Authorization Code Flow + PKCE + Nonce
  • 自动 Discovery(无需手动配置端点)
  • ID Token 验证 + UserInfo 获取(email、name 等)
  • 登录成功后颁发自家 Access + Refresh Token(统一认证,与前文 Refresh Token 系统兼容)
  • 支持角色映射(Keycloak Realm/Client Roles)
  • 高可维护性:专用 crate axum-keycloak-auth(推荐,自动 JWKS Discovery + 角色检查)

Keycloak 服务端配置步骤(2026 版,适用于 Keycloak 26+)

  1. 启动 Keycloak(推荐 Docker):

    docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:latest start-dev
  2. 访问 Admin Consolehttp://localhost:8080 → 创建 Realm(例如 myrealm

  3. 创建 Client

  • Clients → Create Client
  • Client type: OpenID Connect
  • Client ID: axum-app(自定义)
  • Capability config:
    • Client authentication: On(Confidential)
    • Standard flow: Enabled
  • Login settings:
    • Valid redirect URIs: https://your-axum-app.com/api/oidc/callback(精确匹配,生产禁用通配符)
    • Valid post logout redirect URIs: +(允许所有)或指定
    • Web origins: +(CORS 允许)
  1. 获取 Client Secret:Credentials 标签 → 复制 Secret

  2. 创建用户与角色(可选):Users → Add user → 设置密码、分配 Realm Roles(如 useradmin

  3. Discovery Endpointhttp://localhost:8080/realms/myrealm/.well-known/openid-configuration

Rust Axum 客户端集成(推荐 axum-keycloak-auth crate)

此 crate 专为 Keycloak 设计,支持自动 JWKS Discovery、角色检查、高性能。

Cargo.toml

[package]
name = "axum-keycloak-oidc"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.8"
axum_server = "0.7"
tokio = { version = "1.48.0", features = ["full"] }
tower-http = { version = "0.6.8", features = ["trace", "cors"] }
axum-keycloak-auth = "0.2"  # 最新版,支持自动 Discovery
rustls = "0.23.35"
rustls-pemfile = "2.2.0"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"

src/main.rs

use axum::{
    extract::Extension,
    response::IntoResponse,
    routing::get,
    Router,
};
use axum_keycloak_auth::{
    decode_token, expect_role, KeycloakAuthInstance, KeycloakAuthLayer, PassthroughMode, Role,
};
use axum_server::tls_rustls::RustlsConfig;
use std::sync::Arc;
use tower_http::{cors::CorsLayer, trace::TraceLayer};

// 定义角色枚举(匹配 Keycloak Realm/Client Roles)
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum AppRole {
    User,
    Admin,
    #[allow(dead_code)]
    Unknown(String),
}

impl Role for AppRole {
    fn from_str(role: &str) -> Self {
        match role {
            "user" => AppRole::User,
            "admin" => AppRole::Admin,
            _ => AppRole::Unknown(role.to_string()),
        }
    }
}

async fn public() -> impl IntoResponse {
    "This is public endpoint"
}

async fn protected(
    Extension(token): Extension<axum_keycloak_auth::KeycloakToken<AppRole>>,
) -> impl IntoResponse {
    expect_role!(&token, AppRole::User);  // 必须有 user 角色
    format!("Hello {}! Your subject: {}", token.preferred_username(), token.subject())
}

async fn admin(
    Extension(token): Extension<axum_keycloak_auth::KeycloakToken<AppRole>>,
) -> impl IntoResponse {
    expect_role!(&token, AppRole::Admin);  // 必须有 admin 角色
    "Welcome Admin!"
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    // Keycloak 配置(仅需 Issuer URL,自动 Discovery)
    let keycloak_instance = KeycloakAuthInstance::new("http://localhost:8080/realms/myrealm")
        .await?;

    let keycloak_instance = Arc::new(keycloak_instance);

    let cors = CorsLayer::permissive();  // 生产严格配置

    let tls_config = RustlsConfig::from_pem_file("certs/fullchain.pem", "certs/privkey.pem").await?;

    let app = Router::new()
        .route("/public", get(public))
        .route("/protected", get(protected))
        .route("/admin", get(admin))
        .layer(cors)
        .layer(
            KeycloakAuthLayer::<AppRole>::builder()
                .instance(keycloak_instance.clone())
                .passthrough_mode(PassthroughMode::Block)  // 未认证直接拒绝
                .build(),
        )
        .layer(TraceLayer::new_for_http());

    let addr = ([0, 0, 0, 0], 443).into();
    println!("Axum with Keycloak OIDC running on https://{}", addr);

    axum_server::bind_rustls(addr, tls_config)
        .serve(app.into_make_service())
        .await?;

    Ok(())
}

优势与最佳实践

  • 自动 JWKS Discovery:无需手动管理公钥
  • 角色检查宏expect_role! 编译时安全
  • 高性能:异步验证,零拷贝
  • 生产安全
    • 精确 Redirect URI(禁用 *
    • HTTPS 强制(TLS 已启用)
    • Client Secret 保密(Confidential Client)
    • 可扩展:结合前文 Refresh Token 系统

测试流程

  1. 访问 /protected → 重定向到 Keycloak 登录
  2. 登录成功 → 返回 Axum 应用,显示用户信息
  3. 访问 /admin → 仅 admin 角色用户通过

参考资料

现在你的 Axum 服务已完美集成 Keycloak OIDC:安全、标准、易维护,完全符合 2026 年工业级要求。

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)