Merge pull request #423 from mposolda/master

Updated documentation and added example for Authentication SPI
This commit is contained in:
Stian Thorgersen 2014-05-28 09:12:01 +01:00
commit 5120549005
11 changed files with 321 additions and 0 deletions

View file

@ -23,6 +23,9 @@
<!ENTITY CORS SYSTEM "modules/cors.xml">
<!ENTITY Timeouts SYSTEM "modules/timeouts.xml">
<!ENTITY Audit SYSTEM "modules/audit.xml">
<!ENTITY Authentication SYSTEM "modules/authentication-spi.xml">
<!ENTITY Ldap SYSTEM "modules/ldap.xml">
<!ENTITY ExportImport SYSTEM "modules/export-import.xml">
]>
<book>
@ -103,6 +106,9 @@ This one is short
&CORS;
&Timeouts;
&Audit;
&Authentication;
&Ldap;
&ExportImport;
&Migration;
</book>

View file

@ -0,0 +1,66 @@
<chapter id="authentication-spi">
<title>Authentication SPI</title>
<para>
Keycloak provides Authentication SPI, which allows to choose the <literal>AuthenticationProvider</literal> for authenticating users.
AuthenticationProvider is the interface, which states how will be your usernames/passwords validated. You can choose from
the set of available AuthenticationProviders or you can even implement and plug your own AuthenticationProvider, which
will allow to provide your own way how will Keycloak validates users and their passwords.
</para>
<section id="authentication-available-providers">
<title>Available Authentication Providers</title>
<para>
<itemizedlist>
<listitem><literal>Model</literal> - This provider validates users and their passwords based on the Keycloak model. So it just delegates
to model implementation provided either by RDBMS or Mongo at this moment. This is default AuthenticationProvider,
which is configured for <literal>keycloak-admin</literal> realm by default and it's also automatically configured for newly created realms.
</listitem>
<listitem><literal>External-model</literal> - This provider also uses Keycloak model, but it uses different realm to validate your users against.
For example if you want to create new realm "foo" and you want all users of already existing realm "bar" that they are automatically
able to login into realm "foo" with their usernames and passwords, you can choose this provider.
</listitem>
<listitem><literal>Picketlink</literal> - This provider delegates Authentication to <ulink url="http://docs.jboss.org/picketlink/2/latest/reference/html-single/#chap-Identity_Management_-_Overview">Picketlink IDM</ulink>
framework. Right now, Picketlink IDM in Keycloak is configured to always use LDAP based Identity store, which means that picketlink provider
allows you to authenticate your users against LDAP server. Note that you will first need to configure LDAP server as described
<link linkend="ldap">here</link> . <literal>PicketlinkAuthenticationProvider</literal> configured for the realm will automatically use LDAP configuration for this realm.
</listitem>
</itemizedlist>
</para>
</section>
<section id="authentication-features">
<title>Features and configuration</title>
<para>
<itemizedlist>
<listitem>
You can configure AuthenticationProviders separately for each realm. So for example you can choose that just realm
"foo" will use <literal>PicketlinkAuthenticationProvider</literal> and authenticate users against LDAP but realm "keycloak-admin" will still use default <literal>ModelAuthenticationProvider</literal>.
</listitem>
<listitem>
There is also possibility to choose more authentication providers for the realm, which actually means that Keycloak
will use first available AuthenticationProvider and just in case that user doesn't exist here,
it will fallback to second AuthenticationProvider in chain. So this may allow for example scenario, in which
you authenticate user against Keycloak database (model) and just if he doesn't exist in database, it will fallback to LDAP (picketlink).
</listitem>
<listitem>
You can configure for each AuthenticationProvider if you want to update passwords - option <literal>passwordUpdateSupported</literal>.
This means that when user update password or his profile through Keycloak UI, this change will be propagated into AuthenticationProvider.
So for example password in LDAP will be updated if it's <literal>true</literal>, but for read-only LDAP, you will likely switch it to <literal>false</literal>.
It also means that newly registered users will be propagated to particular AuthenticationProvider too,
but note that each user is always bind just to one AuthenticationProvider.
</listitem>
<listitem>
You can add/edit/remove AuthenticationProviders in the <literal>Authentication</literal> tab in admin console, which is under URL
<ulink url="http://localhost:8080/auth/admin/keycloak-admin/console/#/realms/YOUR_REALM/auth-settings">http://localhost:8080/auth/admin/keycloak-admin/console/#/realms/YOUR_REALM/auth-settings</ulink>
</listitem>
</itemizedlist>
</para>
</section>
<section id="authentication-new-provider">
<title>Creating your own Authentication Provider</title>
<para>
You need to implement interface AuthenticationProvider and add the name of your AuthenticationProviderFactory class into
<literal>META-INF/services/org.keycloak.authentication.AuthenticationProviderFactory</literal> file inside your JAR with AuthenticationProvider. You also need to copy this JAR into
<literal>standalone/deployments/auth-server.war/WEB-INF/lib</literal> . The best is to look at <ulink url="https://github.com/keycloak/keycloak/tree/master/examples/providers/authentication-properties">example</ulink> and try it out.
</para>
</section>
</chapter>

View file

@ -0,0 +1,31 @@
<chapter id="export-import">
<title>Export and Import</title>
<para>
Export/import is useful especially if you want to migrate your whole Keycloak database from one environment to another or migrate to different database (For example from MySQL to Oracle).
You can trigger export/import at startup of Keycloak server and it's configurable with System properties right now. The fact it's done at server startup means that no-one can access Keycloak UI or REST endpoints
and edit Keycloak database on the fly when export or import is in progress. Otherwise it could lead to inconsistent results.
</para>
<para>
You can export/import your database either to directory on local filesystem (useful just for testing purposes or if your filesystem is properly protected)
or to encrypted ZIP file on local filesystem. Encrypted ZIP is recommended as export contains many sensitive informations like passwords of your users (even if they are hashed),
but also their email addresses, and especially private keys of the realms.
</para>
<para>
So to export the content of your Keycloak database into encrypted ZIP, you can execute Keycloak server with the System properties like:
<programlisting><![CDATA[
bin/standalone.sh -Dkeycloak.migration.action=export
-Dkeycloak.migration.provider=zip -Dkeycloak.migration.zipFile=<FILE TO EXPORT TO>
-Dkeycloak.migration.zipPassword=<PASSWORD TO DECRYPT EXPORT>
]]></programlisting>
Then you can move or copy the encrypted ZIP file into second environment and you can trigger import from it into Keycloak server with the same command but use
<literal>-Dkeycloak.migration.action=import</literal> instead of <literal>export</literal> .
</para>
<para>
To export into unencrypted directory you can use:
<programlisting><![CDATA[
bin/standalone.sh -Dkeycloak.migration.action=export
-Dkeycloak.migration.provider=dir -Dkeycloak.migration.dir=<DIR TO EXPORT TO>
]]></programlisting>
And similarly for import just use <literal>-Dkeycloak.migration.action=import</literal> instead of <literal>export</literal> .
</para>
</chapter>

View file

@ -0,0 +1,12 @@
<chapter id="ldap">
<title>LDAP Integration</title>
<para>
Right now, LDAP server is configured separately for each Realm. Configuration is in admin console in tab <literal>Ldap</literal>
under realm settings. It's under URL like <ulink url="http://localhost:8080/auth/admin/keycloak-admin/console/index.html#/realms/YOUR_REALM/ldap-settings">http://localhost:8080/auth/admin/keycloak-admin/console/index.html#/realms/YOUR_REALM/ldap-settings</ulink> .
There is nothing like "shared" LDAP server for more realms in Keycloak, but it's planned for the future.
</para>
<para>
LDAP is currently used just for authentication of users done through <literal>PicketlinkAuthenticationProvider</literal> as described <link linkend="authentication-available-providers">here</link> .
In the future, we have plan to have full Sync SPI, which will allow one-way or two-way synchronization between LDAP server and Keycloak database including users and roles.
</para>
</chapter>

View file

@ -0,0 +1,20 @@
Example Authentication Provider based on property file values
=============================================================
* To deploy copy "target/authentication-properties-example.jar" to "standalone/deployments/auth-server.war/WEB-INF/lib" . Then edit "standalone/configuration/keycloak-server.json" and add this:
````shell
"authentication": {
"properties": {
"propertiesFileLocation": "users.properties"
}
}
````
* Then start (or restart)the server. Once started open the admin console, select your realm, then click on "Authentication" and then "Add provider" and select "properties" from the list.
This will mean that realm will use PropertiesAuthenticationProvider for authentication.
* Once you try to login to realm, you can login with username/password like "joe/password1" or "james/password2" . Once joe is authenticated,
you can see in Keycloak admin console in "Users" list that user "joe" was added to the list.
* You can try to login as joe and then go to [http://localhost:8080/auth/realms/keycloak-admin/account/password](http://localhost:8080/auth/realms/keycloak-admin/account/password) and change the password.
You will then be able to logout and login with new password because properties were updated. But this is just in memory-properties, so after server restart the password will be again "password1" .

View file

@ -0,0 +1,45 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>examples-providers-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Properties Authentication Provider Example</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>authentication-properties-example</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authentication-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>authentication-properties-example</finalName>
</build>
</project>

View file

@ -0,0 +1,75 @@
package org.keycloak.examples.providers.authentication;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthProviderStatus;
import org.keycloak.authentication.AuthUser;
import org.keycloak.authentication.AuthenticationProvider;
import org.keycloak.authentication.AuthenticationProviderException;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PropertiesAuthenticationProvider implements AuthenticationProvider {
private static final Logger log = Logger.getLogger(PropertiesAuthenticationProvider.class);
private final Properties properties;
public PropertiesAuthenticationProvider(Properties properties) {
this.properties = properties;
}
@Override
public String getName() {
return "properties";
}
@Override
public List<String> getAvailableOptions() {
return Collections.emptyList();
}
@Override
public AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
if (properties.getProperty(username) != null) {
return new AuthUser(username, username, getName());
} else {
return null;
}
}
@Override
public String registerUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
// Registration ignored
return username;
}
@Override
public AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
String propertyFilePassword = properties.getProperty(username);
if (propertyFilePassword != null && propertyFilePassword.equals(password)) {
return AuthProviderStatus.SUCCESS;
} else {
return AuthProviderStatus.INVALID_CREDENTIALS;
}
}
@Override
public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
// Just update in memory (Won't survive restart)
log.info("Going to update password for user " + username + " in PropertiesAuthenticationProvider");
properties.put(username, password);
return true;
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,62 @@
package org.keycloak.examples.providers.authentication;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.AuthenticationProvider;
import org.keycloak.authentication.AuthenticationProviderFactory;
import org.keycloak.provider.ProviderSession;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PropertiesAuthenticationProviderFactory implements AuthenticationProviderFactory {
private static final Logger log = Logger.getLogger(PropertiesAuthenticationProviderFactory.class);
private Properties properties;
@Override
public AuthenticationProvider create(ProviderSession providerSession) {
return new PropertiesAuthenticationProvider(properties);
}
@Override
public void init(Config.Scope config) {
String propsFileLocation = config.get("propertiesFileLocation");
if (propsFileLocation == null) {
throw new IllegalStateException("Properties file location is not configured in PropertiesAuthenticationProviderFactory");
} else {
log.info("Using properties file: " + propsFileLocation);
}
this.properties = new Properties();
InputStream propertiesStream = null;
try {
propertiesStream = getClass().getClassLoader().getResourceAsStream(propsFileLocation);
this.properties.load(propertiesStream);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
} finally {
if (propertiesStream != null) {
try {
propertiesStream.close();
} catch (IOException e) {
log.error("Error when closing InputStream", e);
}
}
}
}
@Override
public void close() {
}
@Override
public String getId() {
return "properties";
}
}

View file

@ -0,0 +1 @@
org.keycloak.examples.providers.authentication.PropertiesAuthenticationProviderFactory

View file

@ -0,0 +1,2 @@
joe=password1
james=password2

View file

@ -35,5 +35,6 @@
<modules>
<module>audit-listener-sysout</module>
<module>audit-provider-mem</module>
<module>authentication-properties</module>
</modules>
</project>