|
@ -2,18 +2,18 @@
|
|||
<title>Themes</title>
|
||||
|
||||
<para>
|
||||
Keycloak provides theme support for login forms and account management. This allows customizing the look
|
||||
and feel of end-user facing pages so they can be integrated with your brand and applications.
|
||||
Keycloak provides theme support for web pages and emails. This allows customizing the look
|
||||
and feel of end-user facing pages so they can be integrated with your applications.
|
||||
</para>
|
||||
|
||||
<section>
|
||||
<title>Theme types</title>
|
||||
<para>
|
||||
There are several types of themes in Keycloak:
|
||||
A theme can support several types to customize different aspects of Keycloak. The types currently available
|
||||
are:
|
||||
<itemizedlist>
|
||||
<listitem>Account - Account management</listitem>
|
||||
<listitem>Admin - Admin console</listitem>
|
||||
<listitem>Common - Shared resources for themes</listitem>
|
||||
<listitem>Email - Emails</listitem>
|
||||
<listitem>Login - Login forms</listitem>
|
||||
<listitem>Welcome - Welcome pages</listitem>
|
||||
|
@ -28,6 +28,11 @@
|
|||
the theme used for a realm open the <literal>Keycloak Admin Console</literal>, select your realm
|
||||
from the drop-down box in the top left corner. Under <literal>Settings</literal> click on <literal>Theme</literal>.
|
||||
</para>
|
||||
<para>
|
||||
To set the theme for the <literal>master</literal> Keycloak admin console set the admin console theme for
|
||||
the <literal>master</literal> realm. To set the theme for per realm admin access control set the admin console
|
||||
theme for the corresponding realm.
|
||||
</para>
|
||||
<para>
|
||||
To change the welcome theme you need to edit <literal>standalone/configuration/keycloak-server.json</literal>
|
||||
and add <literal>welcomeTheme</literal> to the theme element, for example:
|
||||
|
@ -43,9 +48,8 @@
|
|||
<section>
|
||||
<title>Default themes</title>
|
||||
<para>
|
||||
Keycloak comes bundled with default themes in <literal>standalone/configuration/themes</literal>. It is
|
||||
not recommended to edit these themes directly. Instead you should create a new theme to extend a default
|
||||
theme. A good reference is to copy the keycloak themes as these extend the base theme to add styling.
|
||||
Keycloak comes bundled with default themes in <literal>standalone/configuration/themes</literal>. You should
|
||||
not edit the bundled themes directly. Instead create a new theme that extends a bundled theme.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
|
@ -65,24 +69,63 @@
|
|||
<para>
|
||||
A theme can extend another theme. When extending a theme you can override individual files (templates, stylesheets, etc.).
|
||||
The recommended way to create a theme is to extend the base theme. The base theme provides templates
|
||||
and a default message bundle. It should be possible to achieve the customization required by styling these
|
||||
templates.
|
||||
and a default message bundle. If you decide to override templates bear in mind that you may need to update
|
||||
your templates when upgrading to a new release to include any changes made to the original template.
|
||||
</para>
|
||||
<para>
|
||||
To create a new theme, create a folder in <literal>.../standalone/configuration/themes/<theme type></literal>.
|
||||
The name of the folder is the name of the theme. Then create a file <literal>theme.properties</literal> inside the theme folder.
|
||||
The contents of the file should be:
|
||||
Before creating a theme it's a good idea to disable caching as this makes it possible to edit theme resources
|
||||
without restarting the server. To do this open <literal>../standalone/configuration/keycloak-server.json</literal>
|
||||
for <literal>theme</literal> set <literal>staticMaxAge</literal> to <literal>-1</literal> and
|
||||
<literal>cacheTemplates</literal> and <literal>cacheThemes</literal> to <literal>false</literal>. For example:
|
||||
<programlisting>[<![CDATA[
|
||||
"theme": {
|
||||
"default": "keycloak",
|
||||
"staticMaxAge": 01,
|
||||
"cacheTemplates": false,
|
||||
"cacheThemes": false,
|
||||
"folder": {
|
||||
"dir": "${jboss.server.config.dir}/themes"
|
||||
}
|
||||
},
|
||||
]]></programlisting>
|
||||
Remember to re-enable caching in production as it will significantly impact performance.
|
||||
</para>
|
||||
<programlisting>parent=base</programlisting>
|
||||
<para>
|
||||
You have now created your theme. Check that it works by configuring it for a realm. It should look the same
|
||||
as the base theme as you've not added anything to it yet. The next sections will describe how to modify
|
||||
the theme.</para>
|
||||
To create a new theme create a directory for the theme in <literal>.../standalone/configuration/themes</literal>.
|
||||
The name of the directory should be the name of the theme. For example to create a theme called <literal>example-theme</literal>
|
||||
create the directory <literal>.../standalone/configuration/themes/example-theme</literal>. Inside the theme
|
||||
directory you then need to create a directory for each of the types your theme is going to provide. For example
|
||||
to add the login type to the <literal>example-theme</literal> theme create the directory
|
||||
<literal>.../standalone/configuration/themes/example-theme/login</literal>.
|
||||
</para>
|
||||
<para>
|
||||
For each type create a file <literal>theme.properties</literal> which allows setting some configuration for
|
||||
the theme, for example what theme it overrides and if it should import any themes. For the above example we
|
||||
want to override the base theme and import common resources from the Keycloak theme. To do this create the
|
||||
file <literal>.../standalone/configuration/themes/example-theme/login/theme.properties</literal> with the
|
||||
following contents:
|
||||
<programlisting>[<![CDATA[
|
||||
parent=base
|
||||
import=common/keycloak
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
You have now created a theme with support for the login type. To check that it works open the admin console.
|
||||
Select your realm and click on <literal>Themes</literal>. For <literal>Login Theme</literal> select
|
||||
<literal>example-theme</literal> and click <literal>Save</literal>. Then open the login page for the realm.
|
||||
You can do this either by login through your application or by opening <literal>http://localhost:8080/realms/<realm name>/account</literal>.
|
||||
</para>
|
||||
<para>
|
||||
To see the effect of changing the parent theme, set <literal>parent=keycloak</literal> in <literal>theme.properties</literal>
|
||||
and refresh the login page. To follow the rest of the documentation set it back to <literal>parent=base</literal>
|
||||
before continuing.
|
||||
</para>
|
||||
<section>
|
||||
<title>Stylesheets</title>
|
||||
<para>
|
||||
A theme can have one or more stylesheets, to add a stylesheet create a file inside <literal>resources/css</literal> (for example <literal>resources/css/styles.css</literal>)
|
||||
inside your theme folder. Then registering it in <literal>theme.properties</literal> by adding:
|
||||
A theme can have one or more stylesheets, to add a stylesheet create a file inside <literal>resources/css</literal>
|
||||
(for example <literal>resources/css/styles.css</literal>) inside your theme folder. Then registering it
|
||||
in <literal>theme.properties</literal> by adding:
|
||||
</para>
|
||||
<programlisting>styles=css/styles.css</programlisting>
|
||||
<para>
|
||||
|
@ -90,6 +133,17 @@
|
|||
as you want. For example:
|
||||
</para>
|
||||
<programlisting>styles=css/styles.css css/more-styles.css</programlisting>
|
||||
For the example-theme above add <literal>example-theme/login/resources/css/styles.css</literal> with the
|
||||
following content:
|
||||
<programlisting>[<![CDATA[
|
||||
#kc-form {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
}]]></programlisting>
|
||||
Then edit <literal>example-theme/login/theme.properties</literal> and add <programlisting>styles=css/styles.css</programlisting>.
|
||||
Refresh the login page to see your changes. It's not pretty, but you can see how easily you can modify the
|
||||
styles for your theme.
|
||||
</section>
|
||||
<section>
|
||||
<title>Scripts</title>
|
||||
|
@ -121,9 +175,8 @@
|
|||
<section>
|
||||
<title>Messages</title>
|
||||
<para>
|
||||
Text in the templates are loaded from message bundles. Currently internationalization isn't supported,
|
||||
but that will be added in a later release. A theme that extends another theme will inherit all messages
|
||||
from the parents message bundle, but can override individual messages. For example to replace
|
||||
Text in the templates are loaded from message bundles. A theme that extends another theme will inherit
|
||||
all messages from the parents message bundle, but can override individual messages. For example to replace
|
||||
<literal>Username</literal> on the login form with <literal>Your Username</literal> create the file
|
||||
<literal>messages/messages.properties</literal> inside your theme folder and add the following content:
|
||||
</para>
|
||||
|
@ -134,30 +187,79 @@
|
|||
<para>
|
||||
Keycloak uses <ulink url="http://freemarker.org">Freemarker Templates</ulink> in order to generate HTML.
|
||||
These templates are defined in <literal>.ftl</literal> files and can be overriden from the base theme.
|
||||
Check out the Freemarker website on how to form a template file.
|
||||
Check out the Freemarker website on how to form a template file. To override the login template for the
|
||||
<literal>example-theme</literal> copy <literal>../standalone/configuration/themes/base/login/login.ftl</literal>
|
||||
to <literal>../standalone/configuration/themes/example-theme/login</literal> and open it in an editor. After
|
||||
the first line (<#import ...>) add <literal><h1>HELLO WORLD!</h1></literal> then refresh
|
||||
the page.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Deploying themes</title>
|
||||
<para>
|
||||
Themes can be deployed to Keycloak by copying the theme directory to <literal>../standalone/configuration/themes</literal>
|
||||
or it can be deployed as a module. For a single server or during development just copying the theme is fine, but
|
||||
in a cluster or domain it's recommended to deploy as a module.
|
||||
</para>
|
||||
<para>
|
||||
To deploy a theme as a module you need to create an jar (it's basically just a zip with jar extension) with
|
||||
the theme resources and a file <literal>META/keycloak-server.json</literal> that describes the themes contained
|
||||
in the archive. For example <literal>example-theme.jar</literal> with the contents:
|
||||
<itemizedlist>
|
||||
<listitem>META-INF/keycloak-themes.json</listitem>
|
||||
<listitem>theme/example-theme/login/theme.properties</listitem>
|
||||
<listitem>theme/example-theme/login/login.ftl</listitem>
|
||||
<listitem>theme/example-theme/login/resources/css/styles.css</listitem>
|
||||
</itemizedlist>
|
||||
The contents of META-INF/keycloak-server.json in this case would be:
|
||||
<programlisting>[<![CDATA[
|
||||
{
|
||||
"themes": [{
|
||||
"name" : "example-theme",
|
||||
"types": [ "login" ]
|
||||
}]
|
||||
}
|
||||
]]></programlisting>
|
||||
As you can see a single jar can contain multiple themes and each theme can support one or more types.
|
||||
</para>
|
||||
<para>
|
||||
The deploy the jar as a module to Keycloak you can either manually create the module or use <literal>jboss-cli</literal>.
|
||||
It's simplest to use <literal>jboss-cli</literal> as it creates the required directories and module descriptor
|
||||
for you. To deploy the above jar <literal>jboss-cli</literal> run:
|
||||
<programlisting>[<![CDATA[
|
||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.example.exampletheme --resources=example-theme.jar"
|
||||
]]></programlisting>
|
||||
If you're on windows run <programlisting>KEYCLOAK_HOME/bin/jboss-cli.bat</programlisting>.
|
||||
</para>
|
||||
<para>
|
||||
This command creates <literal>modules/org/example/exampletheme/main</literal> containing <literal>example-theme.jar</literal>
|
||||
and <literal>module.xml</literal>.
|
||||
</para>
|
||||
<para>
|
||||
Once you've created the module you need to register it with Keycloak do this by editing
|
||||
<literal>../standalone/configuration/keycloak-server.json</literal> and adding the module to <literal>theme/module/modules</literal>. For example:
|
||||
<programlisting>[<![CDATA[
|
||||
"theme": {
|
||||
...
|
||||
"module": {
|
||||
"modules": [ "org.example.exampletheme" ]
|
||||
}
|
||||
}
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
If a theme is deployed to <literal>../standalone/configuration/themes</literal> and as a module the first
|
||||
is used.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>SPIs</title>
|
||||
<para>
|
||||
For full control of login forms and account management Keycloak provides a number of SPIs.
|
||||
</para>
|
||||
<section>
|
||||
<title>Theme SPI</title>
|
||||
<para>
|
||||
The Theme SPI allows creating different mechanisms to load themes for the default FreeMarker based
|
||||
implementations of login forms and account management. To create a theme provider you will need to implement
|
||||
<literal>org.keycloak.freemarker.ThemeProviderFactory</literal> and <literal>org.keycloak.freemarker.ThemeProvider</literal>.
|
||||
</para>
|
||||
<para>
|
||||
Keycloak comes with two theme providers, one that loads themes from the classpath (used by default themes)
|
||||
and another that loads themes from a folder (used by custom themes). Looking at these
|
||||
would be a good place to start to create your own theme provider. You can find them inside
|
||||
<literal>forms/common-themes</literal> on GitHub or the source download.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Account SPI</title>
|
||||
<para>
|
||||
|
|
|
@ -21,7 +21,7 @@ Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and regis
|
|||
}
|
||||
}
|
||||
|
||||
Alternatively you can copy `src/main/resources/theme/login` to `standalone/configuration/themes/login/`.
|
||||
Alternatively you can copy `src/main/resources/theme/sunrise` to `standalone/configuration/themes/`.
|
||||
|
||||
Once you've added the theme open the admin console, select your realm, click on `Theme`. In the dropdown for `Login Theme` select `sunrise`. Click `Save` and login to the realm to see the new theme in action.
|
||||
|
||||
|
@ -46,13 +46,7 @@ Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and regis
|
|||
}
|
||||
}
|
||||
|
||||
Alternatively you can copy:
|
||||
|
||||
* `account/logo-example` to `standalone/configuration/themes/account/`
|
||||
* `login/logo-example` to `standalone/configuration/themes/login/`
|
||||
* `admin/logo-example` to `standalone/configuration/themes/admin/`
|
||||
* `welcome/logo-example` to `standalone/configuration/themes/welcome/`
|
||||
|
||||
Alternatively you can copy `src/main/resources/theme/logo-example` to `standalone/configuration/themes/`.
|
||||
|
||||
Once you've added the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 344 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
@ -11,9 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
*/
|
||||
public class ExtendingThemeManagerFactory implements ThemeProviderFactory {
|
||||
|
||||
private ConcurrentHashMap<ThemeKey, Theme> themeCache = new ConcurrentHashMap<ThemeKey, Theme>();
|
||||
|
||||
private ExtendingThemeManager themeManager;
|
||||
private ConcurrentHashMap<ThemeKey, Theme> themeCache;
|
||||
|
||||
@Override
|
||||
public ThemeProvider create(KeycloakSession session) {
|
||||
|
@ -23,7 +21,7 @@ public class ExtendingThemeManagerFactory implements ThemeProviderFactory {
|
|||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
if(Config.scope("theme").getBoolean("cacheThemes", true)) {
|
||||
themeCache = new ConcurrentHashMap<ThemeKey, Theme>();
|
||||
themeCache = new ConcurrentHashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public class FreeMarkerUtil {
|
|||
|
||||
public FreeMarkerUtil() {
|
||||
if (Config.scope("theme").getBoolean("cacheTemplates", true)) {
|
||||
cache = new ConcurrentHashMap<String, Template>();
|
||||
cache = new ConcurrentHashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,11 @@ package org.keycloak.theme;
|
|||
|
||||
import org.keycloak.freemarker.Theme;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -42,7 +40,7 @@ public class ClassLoaderTheme implements Theme {
|
|||
this.type = type;
|
||||
this.classLoader = classLoader;
|
||||
|
||||
String themeRoot = "theme/" + type.toString().toLowerCase() + "/" + name + "/";
|
||||
String themeRoot = "theme/" + name + "/" + type.toString().toLowerCase() + "/";
|
||||
|
||||
this.templateRoot = themeRoot;
|
||||
this.resourceRoot = themeRoot + "resources/";
|
||||
|
@ -60,10 +58,6 @@ public class ClassLoaderTheme implements Theme {
|
|||
}
|
||||
}
|
||||
|
||||
public ClassLoaderTheme() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
|
|
|
@ -18,11 +18,13 @@ public class FolderTheme implements Theme {
|
|||
private String parentName;
|
||||
private String importName;
|
||||
private File themeDir;
|
||||
private String name;
|
||||
private Type type;
|
||||
private final Properties properties;
|
||||
|
||||
public FolderTheme(File themeDir, Type type) throws IOException {
|
||||
public FolderTheme(File themeDir, String name, Type type) throws IOException {
|
||||
this.themeDir = themeDir;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.properties = new Properties();
|
||||
|
||||
|
@ -36,7 +38,7 @@ public class FolderTheme implements Theme {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return themeDir.getName();
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,10 +15,10 @@ import java.util.Set;
|
|||
*/
|
||||
public class FolderThemeProvider implements ThemeProvider {
|
||||
|
||||
private File rootDir;
|
||||
private File themesDir;
|
||||
|
||||
public FolderThemeProvider(File rootDir) {
|
||||
this.rootDir = rootDir;
|
||||
public FolderThemeProvider(File themesDir) {
|
||||
this.themesDir = themesDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -28,51 +28,41 @@ public class FolderThemeProvider implements ThemeProvider {
|
|||
|
||||
@Override
|
||||
public Theme getTheme(String name, Theme.Type type) throws IOException {
|
||||
if (hasTheme(name, type)) {
|
||||
return new FolderTheme(new File(getTypeDir(type), name), type);
|
||||
}
|
||||
return null;
|
||||
File themeDir = getThemeDir(name, type);
|
||||
return themeDir.isDirectory() ? new FolderTheme(themeDir, name, type) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> nameSet(Theme.Type type) {
|
||||
File typeDir = getTypeDir(type);
|
||||
if (typeDir != null) {
|
||||
File[] themes = typeDir.listFiles(new FileFilter() {
|
||||
final String typeName = type.name().toLowerCase();
|
||||
File[] themeDirs = themesDir.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.isDirectory();
|
||||
return pathname.isDirectory() && new File(pathname, typeName).isDirectory();
|
||||
}
|
||||
});
|
||||
|
||||
if (themeDirs != null) {
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (File t : themes) {
|
||||
names.add(t.getName());
|
||||
for (File themeDir : themeDirs) {
|
||||
names.add(themeDir.getName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private File getTypeDir(Theme.Type type) {
|
||||
if (rootDir != null && rootDir.isDirectory()) {
|
||||
File typeDir = new File(rootDir, type.name().toLowerCase());
|
||||
if (typeDir.isDirectory()) {
|
||||
return typeDir;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTheme(String name, Theme.Type type) {
|
||||
File typeDir = getTypeDir(type);
|
||||
return typeDir != null && new File(typeDir, name).isDirectory();
|
||||
return getThemeDir(name, type).isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private File getThemeDir(String name, Theme.Type type) {
|
||||
return new File(themesDir, name + File.separator + type.name().toLowerCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
|