Keycloak 默认登录页比较朴素,好在它提供了完善的主题机制,可以自由定制登录、注册、忘记密码等页面样式。本文以 Keycloak 26.x 为例,介绍从零开始定制一个 Realm 登录页的完整流程。
Keycloak 主题存放在 themes/ 目录下。创建一个自定义主题的最小结构:
themes/
└── my-theme/
├── theme.properties # 主题根配置
└── login/
├── theme.properties # 登录页配置
├── login.ftl # 登录页模板
├── messages/
│ └── messages_zh_CN.properties # 中文本地化
└── resources/
└── css/
└── login.css # 样式文件
parent=keycloak
parent=keycloak 表示继承 Keycloak 内置主题。未自定义的部分(如注册页、邮件模板)会自动使用父主题。
parent=keycloak
styles=css/login.css
这里引入自定义 CSS 文件。可以列出多个样式文件,用空格分隔。
Keycloak 使用 Apache FreeMarker 作为模板引擎。登录页的核心是 login.ftl:
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=true; section>
<#if section = "header">
<#-- 留空,我们自己写 header -->
<#elseif section = "form">
<div class="kc-login-card">
<div class="kc-brand">
<h1>${msg("loginTitleHtml")}</h1>
<p>${msg("loginAccountTitle")}</p>
</div>
<form action="${url.loginAction}" method="post">
<div class="kc-field">
<label for="username">${msg("usernameOrEmail")}</label>
<input id="username" name="username" type="text"
value="${(login.username!'')}">
</div>
<div class="kc-field">
<label for="password">${msg("password")}</label>
<input id="password" name="password" type="password">
</div>
<button type="submit">${msg("doLogIn")}</button>
</form>
</div>
</#if>
</@layout.registrationLayout>
重要:Keycloak 父模板会在你的 FTL 外面包裹一层 <div class="card-pf">,写 CSS 时要注意把外层重置为透明,避免双层卡片嵌套。
在 login/messages/messages_zh_CN.properties 中定义中文文本:
doLogIn=登 录
doRegister=注 册
usernameOrEmail=用户名或邮箱
username=用户名
password=密码
forgotPassword=忘记密码?
noAccount=还没有账号?
registerNow=立即注册
loginTitle=登录
loginTitleHtml=登录
loginAccountTitle=使用火金科技账号
同时需要在 Keycloak Admin 中为 Realm 启用国际化:
Internationalizationzh-CNzh-CN、enKeycloak 页面结构是 <html class="login-pf"> → <div class="login-pf-page"> → <div class="card-pf"> → 你的 kc-login-card。
写 CSS 时需要注意:
.login-pf 在 <html> 上,不能设 width,否则整个页面压缩到左侧.card-pf 是父模板生成的外层容器,需要重置为透明.kc-login-card 上推荐做法:
/* 重置外层 */
.card-pf {
background: transparent;
border: none;
box-shadow: none;
padding: 0;
width: 100%;
max-width: 400px;
}
/* 你的卡片 */
.kc-login-card {
background: #fff;
border-radius: 16px;
padding: 48px 40px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
/* 隐藏默认元素 */
#kc-header, #kc-header-wrapper,
#kc-locale { display: none !important; }
推荐使用 bind mount 将主题目录挂载到 Keycloak 容器:
docker run ... \
-v /opt/workspace/themes/my-theme:/opt/keycloak/themes/my-theme:ro
这样主题文件放在宿主机上,可以用 Git 管理版本:
# 宿主机
cd /opt/workspace/themes/my-theme
git pull # 更新主题
# 重启 Keycloak 使主题生效
docker restart keycloak
Keycloak 会缓存主题资源(CSS/JS),更新后需要重启容器清除缓存。
通过 Admin Console:Realm Settings → Themes → Login Theme,选择你的主题名称。
或者通过 API:
curl -X PUT https://auth.example.com/admin/realms/{realm} \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{"loginTheme":"my-theme"}'
ls /opt/keycloak/themes/ 验证).login-pf 没有设置 width(它在 html 元素上).card-pf 是否正确重置messages_zh_CN.properties 文件编码为 UTF-8