-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
011a8c9
commit af6da95
Showing
18 changed files
with
1,964 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,16 @@ | ||
# http-keycloak-userstorage-spi | ||
An example for having a custom user storage for Keycloak users, groups and roles | ||
|
||
An example for having a custom user storage for Keycloak users, groups and roles. | ||
|
||
Keycloak allows to have your own UserStorage backend. So you don't need to have an AD or LDAP. A possible use-case is for example an existing legacy system with its own user storage and you want to build some new services around it. When you then start with OIDC, you can use Keycloak as a OIDC provider and that can use your legacy backend for user storage. | ||
|
||
This project uses the Keycloak UserStorage service provider interface to allow an HTTP client to read users from such a backend. | ||
|
||
The backend itself needs at least this REST endpoints. | ||
|
||
- GET /user - returns a list of HTTPUserModel. It supports paging and filtering (offset, limit) with the query param search for random search or group. | ||
- GET /user/{username} - returns a single HTTPUserModel that matches the given username. If the HTTP response is not 200, there is no match. | ||
- GET /user/mail/{email} - returns a single HTTPUserModel that matches the given mail address. If the HTTP response is not 200, there is no match. | ||
- POST /user/validate/{username} - the POST body contains the password. This is used for validating the users password. | ||
|
||
The HTTPUserModel contains some basic informations about the user for Keycloak, like the username, first and last name, email and attributes. If you want to apply groups and roles to the user (which is useful, if your services depends on different roles) your backend needs to fill the HashMap<String,List<String>>. Where the key is the group name and the List<String> is the list of role names. Of course you can build complexer GroupModels and RoleModels, if you want. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>http.keycloak</groupId> | ||
<artifactId>userstorage-spi</artifactId> | ||
<version>0.1.0</version> | ||
|
||
<name>Keycloak HTTP UserStoreProvider</name> | ||
<description /> | ||
|
||
<properties> | ||
<java.version>11</java.version> | ||
<maven.compiler.source>11</maven.compiler.source> | ||
<maven.compiler.target>11</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
|
||
<keycloak.version>9.0.3</keycloak.version> | ||
<resteasy.version>4.5.8.Final</resteasy.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-core</artifactId> | ||
<scope>provided</scope> | ||
<version>${keycloak.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-server-spi</artifactId> | ||
<scope>provided</scope> | ||
<version>${keycloak.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-server-spi-private</artifactId> | ||
<scope>provided</scope> | ||
<version>${keycloak.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.keycloak</groupId> | ||
<artifactId>keycloak-kerberos-federation</artifactId> | ||
<scope>provided</scope> | ||
<version>${keycloak.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jboss.resteasy</groupId> | ||
<artifactId>resteasy-client</artifactId> | ||
<version>${resteasy.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jboss.resteasy</groupId> | ||
<artifactId>resteasy-jackson2-provider</artifactId> | ||
<scope>provided</scope> | ||
<version>${resteasy.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jboss.logging</groupId> | ||
<artifactId>jboss-logging</artifactId> | ||
<scope>provided</scope> | ||
<version>3.4.1.Final</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<scope>test</scope> | ||
<version>4.13.1</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jboss.spec.javax.transaction</groupId> | ||
<artifactId>jboss-transaction-api_1.3_spec</artifactId> | ||
<scope>provided</scope> | ||
<version>2.0.0.Final</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.8.0</version> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
79 changes: 79 additions & 0 deletions
79
src/main/java/http/keycloak/userstorage/FreshlyCreatedUsers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package http.keycloak.userstorage; | ||
|
||
|
||
import org.keycloak.models.KeycloakSession; | ||
|
||
import java.util.Optional; | ||
|
||
/** | ||
* All new users who are still not persisted to http storage shall remain in current KeycloakSession. This way, | ||
* if infinispan calls getUserById for the user that hasn't been persisted yet, this class will return the user as | ||
* it was already persisted. | ||
*/ | ||
public class FreshlyCreatedUsers { | ||
|
||
private final KeycloakSession session; | ||
|
||
public FreshlyCreatedUsers(KeycloakSession session) { | ||
this.session = session; | ||
} | ||
|
||
private static boolean isNotBlank(String str) { | ||
return str != null && !str.trim().isEmpty(); | ||
} | ||
|
||
private static String usernameKey(String username) { | ||
return "username:" + username; | ||
} | ||
|
||
private static String emailKey(String email) { | ||
return "email:" + email; | ||
} | ||
|
||
private static String idKey(String id) { | ||
return "id:" + id; | ||
} | ||
|
||
public Optional<HTTPUserModelDelegate> getFreshlyCreatedUserByUsername(String username) { | ||
return Optional.ofNullable(session.getAttribute(usernameKey(username), HTTPUserModelDelegate.class)); | ||
} | ||
|
||
public Optional<HTTPUserModelDelegate> getFreshlyCreatedUserById(String id) { | ||
return Optional.ofNullable(session.getAttribute(idKey(id), HTTPUserModelDelegate.class)); | ||
} | ||
|
||
public Optional<HTTPUserModelDelegate> getFreshlyCreatedUserByEmail(String email) { | ||
return Optional.ofNullable(session.getAttribute(emailKey(email), HTTPUserModelDelegate.class)); | ||
} | ||
|
||
public void saveInSession(HTTPUserModelDelegate userModel) { | ||
String username = userModel.getUsername(); | ||
if (isNotBlank(username)) { | ||
session.setAttribute(usernameKey(username), userModel); | ||
} | ||
String email = userModel.getEmail(); | ||
if (isNotBlank(email)) { | ||
session.setAttribute(emailKey(email), userModel); | ||
} | ||
String id = userModel.getId(); | ||
if (isNotBlank(id)) { | ||
session.setAttribute(idKey(id), userModel); | ||
} | ||
} | ||
|
||
public void removeFromSession(HTTPUserModelDelegate userModel) { | ||
String username = userModel.getUsername(); | ||
if (isNotBlank(username)) { | ||
session.removeAttribute(usernameKey(username)); | ||
} | ||
String email = userModel.getEmail(); | ||
if (isNotBlank(email)) { | ||
session.removeAttribute(emailKey(email)); | ||
} | ||
String id = userModel.getId(); | ||
if (isNotBlank(id)) { | ||
session.removeAttribute(idKey(id)); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package http.keycloak.userstorage; | ||
|
||
import org.keycloak.common.util.MultivaluedHashMap; | ||
|
||
/** | ||
* A model for all MACHWeb specific configurations | ||
*/ | ||
public class HTTPConfig { | ||
private final MultivaluedHashMap<String, String> config; | ||
|
||
public HTTPConfig(MultivaluedHashMap<String, String> config) { | ||
this.config = config; | ||
} | ||
|
||
public String getUrl() { | ||
return config.getFirst(HTTPConstants.CONFIG_URL); | ||
} | ||
|
||
public String getUsername() { | ||
return config.getFirst(HTTPConstants.CONFIG_USERNAME); | ||
} | ||
|
||
public String getPassword() { | ||
return config.getFirst(HTTPConstants.CONFIG_PASSWORD); | ||
} | ||
|
||
public boolean isPagination() { | ||
// for later - can be configurable | ||
return false; | ||
} | ||
|
||
public int getBatchSizeForSync() { | ||
// for later - can be configurable, if isPagination is true | ||
return 100; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (obj == this) | ||
return true; | ||
if (!(obj instanceof HTTPConfig)) | ||
return false; | ||
|
||
HTTPConfig that = (HTTPConfig) obj; | ||
|
||
if (!config.equals(that.config)) | ||
return false; | ||
return true; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return config.hashCode() * 13; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
MultivaluedHashMap<String, String> copy = new MultivaluedHashMap<String, String>(config); | ||
copy.remove(HTTPConstants.CONFIG_PASSWORD); | ||
return new StringBuilder(copy.toString()).toString(); | ||
} | ||
} |
Oops, something went wrong.