(五)keycloak 自定义主题

系列目录

(一)keycloak 部署运行及源码打包
(二)keycloak 配置运行
(三)keycloak 基于SpringBoot、Servlet的客户端开发
(四)keycloak 自定义用户(SPI)开发
(五)keycloak 自定义主题
(未完成)(六)keycloak 添加登录验证码功能
(七)keycloak 设置客户端访问类型 bearer-only
(八)keycloak 设置客户端访问类型 confidential
(九)keycloak使用nginx来配置https



前言

keycloak是一套完整的开源认证授权管理解决方案,由红帽开发,提供了多种语言库,方便集成。本系列教程以使用为主,介绍keycloak的搭建,源码编译,以及部分功能的二次开发。

keycloak官网提供了详细的教程以及示例,可以参考官网示例进行编写开发。
官网地址 本系列教程基于官网最新版本18.0进行编写。

keycloak提供了一套默认的主题,通常情况下,我们都需要使用自己的登录页面。keycloak的默认主题包整个后台所有的页面,大部分情况下我们的需求只要修改登录页面即可,本章节我们主要讲解下自定义用户登录页面


1.效果图

在这里插入图片描述

2.技术点

  • Freemarker模板语言
  • vue 前端框架
  • ElementUI vuejs的ui框架

3.开发

我们以修改登录页面为例,首先我们到源码中复制一份现有的主题文件,路径/keycloak/themes/src/main/resources/theme
在这里插入图片描述
其中keycloak文件夹是默认的控制台登录主题,如果您只需要一个最简单原始的主题,可以直接复制base文件夹。
打开keycloak文件夹,里面对应了很多子文件夹,根据文件夹的名字我们就能看出对应功能。比如我们本次教程的修改登录页面。
在这里插入图片描述
打开login文件夹,这个文件夹就是登录页面的所有文件
在这里插入图片描述
其中,我们主要修改的是login.ftl 文件,所有的css、images、js 放在了resources文件夹下,还有一个需要注意的地方
theme.properties文件 这个文件配置了一些全局的参数,比如我们需要添加全局样式表。
在这里插入图片描述
比如上图所示的,我们统一引用了elementui的css和js,这里的路径是相对 resources文件夹来说的,
即:styles=css/index.min.css 的实际路径是 ./login/resources/css/index.min.css

那么在配置完成后,我们就可以开始真正的编写自己的登录页面了,打开login.ftl文件。这个ftl的后缀是freemark模板的后缀,语法比较简单,各位自行搜索语法规范。

打开login.ftl文件后,可以看到里面有很多内容,我个人比较倾向于自己重新编写所有内容,然后在关键位置复制原来的变量和判断代码,这样写起来比较清晰。
比如我实际的页面是这样些的
在这里插入图片描述
红框中的内容就是keycloak原来的freemark变量,(freemark语法和变量以$和<#这种写法开头)
是不是很简单。
同样需要注意下,里面有一部分内容在页面上是不显示的,需要在后台开启后才会显示,如果你也需要这些功能,请结合后台一起调试,比如:是否开启忘记密码,是否开启注册,第三方登录等。

附上我的登录页面的全部内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="robots" content="noindex, nofollow">
    <title>${msg("loginTitle",(realm.displayName!''))}</title>
    <link rel="icon" href="${url.resourcesPath}/img/favicon.ico" />
    <link rel="stylesheet" href="${url.resourcesPath}/css/login.css" />
    <link rel="stylesheet" href="${url.resourcesPath}/css/index.min.css" />
    <script src="${url.resourcesPath}/js/vue.min.js" type="text/javascript"></script>
    <script src="${url.resourcesPath}/js/index.min.js" type="text/javascript"></script>
    <script src="${url.resourcesPath}/js/svg-icon.js" type="text/javascript"></script>

    <style>

        .form-item {
            position: relative;
        }

        .form-item-input {
            border: 1px solid #ddd;
            background: #fff;
            border-radius: 5px;
            color: #454545;
            margin: 30px 0px;
        }

        .form-error {
            border-color: red;
            transition-property: border-color;
            transition-duration: 0.5s
        }

        .el-input {
            display: inline-block;
            height: 47px;
            width: 85%;
        }

        .el-input input {
            background: transparent;
            border: 0px;
            -webkit-appearance: none;
            border-radius: 0px;
            padding: 12px 5px;
            color: #333;
            font-size: 16px;
            height: 47px;
            caret-color: #333;
        }

        .login-button {
            width: 100%;
            margin: 0px 0 16px 0;
            padding: 16px 0px;
            height: 48px;
        }
        
        .digital-code-input {
            margin: 0px 24px 0px 0px;
            padding-left: 12px;
            flex: 1;
        }

    </style>
</head>

<#assign displayMessage=!messagesPerField.existsError('username','password')>

<body>
    <div id="app" class="root">
        <div class="left">
            <img class="logo" src="${url.resourcesPath}/img/logo.png" />
            <div class="logo-main">
                <img src="${url.resourcesPath}/img/2-2.png" />
            </div>
        </div>
        <div class="right">
            <div class="login-form">
                <#if realm.password>
                <form ref="loginForm" action="${url.loginAction}" @submit.prevent="submitLogin" method="post">
                    <h3 class="login-title">${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}</h3>

                    <!-- 用户名 -->
                    <#if !usernameHidden??>
                    <div class="form-item">
                        <div class="form-item-input" :class="rule.username ? '' : 'form-error'">
                            <span class="svg-container">
                                <svg class="icon" aria-hidden="true">
                                    <use xlink:href="#icon-yonghu"></use>
                                </svg>
                            </span>
                            <el-input tabindex="1" ref="username" v-model="loginForm.username" @change="checkUserName" placeholder="账号" name="username" type="text" clearable auto-complete="on" />
                        </div>
                        <div v-if="!rule.username" class="el-form-item__error">请输入账号</div>
                    </div>
                    </#if>

                    <div class="form-item">
                        <div class="form-item-input" :class="rule.password ? '' : 'form-error'">
                            <span class="svg-container">
                                <svg class="icon" aria-hidden="true">
                                    <use xlink:href="#icon-mima"></use>
                                </svg>
                            </span>
                            <el-input tabindex="2" ref="password" v-model="loginForm.password" @change="checkPassword" placeholder="密码" name="password" clearable show-password />
                        </div>
                        <div v-if="!rule.password" class="el-form-item__error">请输入密码</div>
                    </div>

                    <!-- 记住,忘记密码,后台开启 -->
                    <div id="kc-form-options" class="remember-box">
                        <#if realm.rememberMe && !usernameHidden??>
                            <el-tooltip class="item" effect="dark" placement="top-start">
                                <div slot="content">在会话有效期间,如果您关闭了浏览器,<br/>再次打开系统后,将自动保持关闭前的状态</div>
                                <div class="checkbox">
                                    <label class="el-checkbox">
                                        <span class="el-checkbox__input">
                                        <#if login.rememberMe??>
                                            <input id="rememberMe" name="rememberMe" type="checkbox" checked>
                                        <#else>
                                            <input id="rememberMe" name="rememberMe" type="checkbox">
                                        </#if>
                                        </span>
                                        <span>${msg("rememberMe")}</span>
                                    </label>
                                </div>
                            </el-tooltip>
                        </#if>

                        <#if realm.resetPasswordAllowed>
                        <div class="forgot-password">
                            <el-link type="primary" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</el-link>
                        </div>
                        </#if>
                    </div>

                    <input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
                    <el-button :loading="loading" type="primary" name="login" class="login-button" round native-type="submit">${msg("doLogIn")}</el-button>

                </form>
                </#if>

                <!-- 其他登录方式 -->
                <#if auth?has_content && auth.showTryAnotherWayLink()>
                    <form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post">
                        <div class="${properties.kcFormGroupClass!}">
                            <input type="hidden" name="tryAnotherWay" value="on"/>
                            <a href="#" id="try-another-way"
                                onclick="document.forms['kc-select-try-another-way-form'].submit();return false;">${msg("doTryAnotherWay")}</a>
                        </div>
                    </form>
                </#if>

                <!-- 第三方登录 -->
                <#if realm.password && social.providers??>
                <div>
                    <ul>
                        <#list social.providers as p>
                            <a id="social-${p.alias}" type="button" href="${p.loginUrl}">
                                <#if p.iconClasses?has_content>
                                <i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
                                <span>${p.displayName!}</span>
                                <#else>
                                <span>${p.displayName!}</span>
                                </#if>
                            </a>
                        </#list>
                    </ul>
                </div>
                </#if>

                <!-- 允许注册,后台开启, 注册按钮 -->
                <#if realm.password && realm.registrationAllowed && !registrationDisabled??>
                <div class="register-user">
                    <el-link type="primary" href="${url.registrationUrl}">${msg("noAccount")} ${msg("doRegister")}</el-link>
                </div>
                </#if>
            </div>
        </div>

        <div class="copyright">© 2022 ChangSir. All Rights Reserved.</div>
    </div>
</body>
<script>
    new Vue({
        el: '#app',
        data: function() {
            return {
                loginForm: {
                    username: "${(login.username!'')}",
                    password: "",
                    codeToken: "",
                },
                rule: {
                    username: true,
                    password: true,
                    codeToken: true,
                },
                loading: false,
            }
        },
        created: function() {
            this.$nextTick(() => {
                //其他错误
                <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
                this.$message({
                    dangerouslyUseHTMLString: true,
                    type: 'error',
                    message: "${kcSanitize(message.summary)?no_esc}"
                })
                //用户密码错误
                <#elseif messagesPerField.existsError('username','password') || (usernameHidden?? && messagesPerField.existsError('username','password'))>
                this.$message({
                    dangerouslyUseHTMLString: true,
                    type: 'error',
                    message: "${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}"
                })
                </#if>
		var req = new XMLHttpRequest(); req.open('GET', document.location.href, false); req.send(null); var headers = req.getAllResponseHeaders(); console.log(headers);

            });
        },
        methods: {

            //检查用户名
            checkUserName(value) {
                this.rule.username = value.trim().length > 0
            },

            //检查密码
            checkPassword(value) {
                this.rule.password = value.trim().length > 0
            },

            //检查验证码
            checkCode(value) {
                this.rule.codeToken = value.trim().length > 0
            },

            //登录
            submitLogin() {
                
                //校验
                this.checkUserName(this.loginForm.username)
                this.checkPassword(this.loginForm.password)

                //全部校验通过
                if(this.rule.username && this.rule.password) {
                    this.$refs.loginForm.submit();
                } else {
                    return
                }
            },

        }
    })
</script>
</html>

4.配置

完成上述开发后,就需要进行配置了。首先我们修改下刚才的文件名(原先的keycloak文件夹),这个文件名就是我们下面在后台中看到的主题名称,请使用英文命名,这里我们命名为test

现在我们打开keycloak的程序,找到themes文件,将test文件夹复制进去
在这里插入图片描述
然后启动我们的keycloak,打开后台,我们以master实例为例,直接修改master的登录页面,也就是我们keycloak控制台的页面。
领域设置 菜单的 主题 标签下,找到登录主题
在这里插入图片描述
点击登录主题,可以看到这里有一个test,就是我们刚才修改的文件夹。
在这里插入图片描述

选择test,然后点击下方的保存,并退出重新登录,这时候你会发现你的登录页面已经变成了你修改的内容了。
聪明的你就完成了keycloak自定义主题开发。
下一节我们会讲下 验证码的配置和开发

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Kubernetes(简称k8s)是一个开源的容器编排引擎,可以用于自动化部署、扩展和管理容器化应用程序。Keycloak是一个开源的身份和访问管理解决方案,它提供了诸如单点登录(SSO)、多因素身份验证、授权等功能。在k8s环境中部署和自定义Keycloak可以帮助我们更好地管理和保护我们的应用程序。 首先,在k8s集群中部署Keycloak的步骤包括创建一个Deployment来运行Keycloak容器、创建一个Service将Keycloak容器暴露出来、配置Ingress来管理对Keycloak的访问。可以通过编写YAML文件来定义Deployment、Service和Ingress资源,然后使用kubectl命令将其部署到k8s集群中。 其次,自定义Keycloak可以包括配置主题、引入自定义的身份提供者、定义客户端等。通过在Keycloak的管理后台进行相应的配置,我们可以定制Keycloak的外观和行为,以满足具体的业务需求。 最后,在部署和自定义Keycloak时需要注意的问题包括配置数据库、管理用户、设置安全策略等。我们需要选择合适的数据库作为Keycloak的持久化存储,并根据实际情况管理用户的访问权限。此外,为了保证应用程序的安全性,我们还需要设置适当的安全策略和监控机制。 总之,通过在k8s集群中部署和自定义Keycloak,我们可以实现统一的身份和访问管理,并且能够满足不同应用程序的特定需求。这不仅可以提高系统的安全性和可靠性,还可以简化用户的身份管理和访问控制流程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChangSir-86

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值