ugrade to Keycloak 19.0.1
This commit is contained in:
parent
2ec76fe137
commit
bf9a740c2b
8 changed files with 50 additions and 55 deletions
|
@ -1,3 +1,10 @@
|
||||||
|
# Keycloak extension for API key authentication
|
||||||
|
|
||||||
|
The extension contains providers for supporting API key authentication, and also other non related providers like a custom `EmailSenderProvider` (for demo purposes).
|
||||||
|
|
||||||
|
It also contains a customization of the account console (the user info page provided by Keycloak) showing the API key. The account console is accessible at `/auth/realms/{realm_name}/account` and requires the user to be already authenticated.
|
||||||
|
|
||||||
|
The master branch uses the new Keycloak distribution powered by Quarkus. For Legacy keycloak (versions < 17.0.0), you can switch to the `legacy` branch.
|
||||||
## How to run
|
## How to run
|
||||||
|
|
||||||
you can run the project by running the following from a terminal: `mvn -f api-key-module package && mvn -f dashboard-service package && docker-compose up`
|
you can run the project by running the following from a terminal: `mvn -f api-key-module package && mvn -f dashboard-service package && docker-compose up`
|
||||||
|
@ -8,6 +15,6 @@ Note: You need to add `auth-server` to your hosts file (`/etc/hosts` for linux)
|
||||||
|
|
||||||
1. Navigate to localhost:8180 in a browser, you will redirected to keycloak for authentication
|
1. Navigate to localhost:8180 in a browser, you will redirected to keycloak for authentication
|
||||||
2. you need register a new user, after which you will be redirected to the main dashboard page which will show your API key
|
2. you need register a new user, after which you will be redirected to the main dashboard page which will show your API key
|
||||||
3. copy the API key and use it to call the API: `curl -v -H "x-api-key: $THE_API_KEY" localhost:8200`, if you omit the API key, you will get 401 status
|
3. copy the API key and use it to call the API: `curl -v -H "x-api-key: $THE_API_KEY" localhost:8280`, if you omit the API key, you will get 401 status
|
||||||
|
|
||||||
More explanations can be found in this blog [post](http://www.zakariaamine.com/2019-06-14/extending-keycloak)
|
More explanations can be found in this blog [post](http://www.zakariaamine.com/2019-06-14/extending-keycloak)
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>11</java.version>
|
<java.version>11</java.version>
|
||||||
<keycloak.version>15.0.2</keycloak.version>
|
<keycloak.version>19.0.1</keycloak.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -4,7 +4,7 @@ package com.gwidgets.providers;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import org.keycloak.common.util.RandomString;
|
import org.keycloak.common.util.SecretGenerator;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventListenerProvider;
|
import org.keycloak.events.EventListenerProvider;
|
||||||
|
@ -23,14 +23,14 @@ public class RegisterEventListenerProvider implements EventListenerProvider {
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
private RealmProvider model;
|
private RealmProvider model;
|
||||||
//keycloak utility to generate random strings, anything can be used e.g UUID,..
|
//keycloak utility to generate random strings, anything can be used e.g UUID,..
|
||||||
private RandomString randomString;
|
private SecretGenerator secretGenerator;
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
|
|
||||||
public RegisterEventListenerProvider(KeycloakSession session) {
|
public RegisterEventListenerProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.model = session.realms();
|
this.model = session.realms();
|
||||||
this.entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
this.entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||||
this.randomString = new RandomString(50);
|
this.secretGenerator = SecretGenerator.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEvent(Event event) {
|
public void onEvent(Event event) {
|
||||||
|
@ -55,8 +55,7 @@ public class RegisterEventListenerProvider implements EventListenerProvider {
|
||||||
|
|
||||||
|
|
||||||
public void addApiKeyAttribute(String userId) {
|
public void addApiKeyAttribute(String userId) {
|
||||||
|
String apiKey = secretGenerator.randomString(50);
|
||||||
String apiKey = randomString.nextString();
|
|
||||||
UserEntity userEntity = entityManager.find(UserEntity.class, userId);
|
UserEntity userEntity = entityManager.find(UserEntity.class, userId);
|
||||||
UserAttributeEntity attributeEntity = new UserAttributeEntity();
|
UserAttributeEntity attributeEntity = new UserAttributeEntity();
|
||||||
attributeEntity.setName("api-key");
|
attributeEntity.setName("api-key");
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.amazonaws.services.simpleemail.model.Message;
|
||||||
import com.amazonaws.services.simpleemail.model.SendEmailRequest;
|
import com.amazonaws.services.simpleemail.model.SendEmailRequest;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailSenderProvider;
|
import org.keycloak.email.EmailSenderProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
@ -24,9 +25,14 @@ public class SESEmailSenderProvider implements EmailSenderProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(Map<String, String> config, UserModel user, String subject, String textBody,
|
public void send(Map<String, String> config, UserModel user, String subject, String textBody,
|
||||||
String htmlBody) {
|
String htmlBody) throws EmailException {
|
||||||
|
this.send(config, user.getEmail(), subject, textBody, htmlBody);
|
||||||
|
}
|
||||||
|
|
||||||
log.info("attempting to send email using aws ses for " + user.getEmail());
|
@Override
|
||||||
|
public void send(Map<String, String> config, String address, String subject, String textBody, String htmlBody) throws EmailException {
|
||||||
|
|
||||||
|
log.info("attempting to send email using aws ses for " + address);
|
||||||
|
|
||||||
Message message = new Message().withSubject(new Content().withData(subject))
|
Message message = new Message().withSubject(new Content().withData(subject))
|
||||||
.withBody(new Body().withHtml(new Content().withData(htmlBody))
|
.withBody(new Body().withHtml(new Content().withData(htmlBody))
|
||||||
|
@ -34,10 +40,10 @@ public class SESEmailSenderProvider implements EmailSenderProvider {
|
||||||
|
|
||||||
SendEmailRequest sendEmailRequest = new SendEmailRequest()
|
SendEmailRequest sendEmailRequest = new SendEmailRequest()
|
||||||
.withSource("example<" + config.get("from") + ">")
|
.withSource("example<" + config.get("from") + ">")
|
||||||
.withMessage(message).withDestination(new Destination().withToAddresses(user.getEmail()));
|
.withMessage(message).withDestination(new Destination().withToAddresses(address));
|
||||||
|
|
||||||
sesClient.sendEmail(sendEmailRequest);
|
sesClient.sendEmail(sendEmailRequest);
|
||||||
log.info("email sent to " + user.getEmail() + " successfully");
|
log.info("email sent to " + address + " successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package com.gwidgets.resources;
|
package com.gwidgets.resources;
|
||||||
|
|
||||||
import java.util.List;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import java.util.Objects;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import java.util.Objects;
|
||||||
import org.keycloak.models.UserModel;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class ApiKeyResource {
|
public class ApiKeyResource {
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ public class ApiKeyResource {
|
||||||
@GET
|
@GET
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response checkApiKey(@QueryParam("apiKey") String apiKey) {
|
public Response checkApiKey(@QueryParam("apiKey") String apiKey) {
|
||||||
List<UserModel> result = session.userStorageManager().searchForUserByUserAttribute("api-key", apiKey, session.realms().getRealm(realmName));
|
Stream<UserModel> result = session.users().searchForUserByUserAttributeStream(session.realms().getRealm(realmName), "api-key", apiKey);
|
||||||
return result.isEmpty() ? Response.status(401).type(MediaType.APPLICATION_JSON).build(): Response.ok().type(MediaType.APPLICATION_JSON).build();
|
return result.count() > 0 ? Response.ok().type(MediaType.APPLICATION_JSON).build(): Response.status(401).type(MediaType.APPLICATION_JSON).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>11</java.version>
|
<java.version>11</java.version>
|
||||||
<keycloak.version>15.0.2</keycloak.version>
|
<keycloak.version>19.0.1</keycloak.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
version: '3.7'
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
auth-server:
|
auth-server:
|
||||||
image: jboss/keycloak:15.0.2
|
image: quay.io/keycloak/keycloak:19.0.1
|
||||||
environment:
|
environment:
|
||||||
KEYCLOAK_USER: admin
|
KEYCLOAK_ADMIN: admin
|
||||||
KEYCLOAK_PASSWORD: admin
|
KEYCLOAK_ADMIN_PASSWORD: admin
|
||||||
JAVA_OPTS_APPEND: "-Dkeycloak.migration.action=import -Dkeycloak.migration.provider=dir -Dkeycloak.migration.dir=/import -Dkeycloak.migration.strategy=IGNORE_EXISTING -Dkeycloak.profile.feature.upload_scripts=enabled"
|
KC_HTTP_ENABLED: "true"
|
||||||
|
KC_HOSTNAME: auth-server
|
||||||
|
#to keep compatible with other services that are expecting /auth
|
||||||
|
KC_HTTP_RELATIVE_PATH: /auth
|
||||||
|
KC_HOSTNAME_STRICT_HTTPS: "false"
|
||||||
|
JAVA_OPTS_APPEND: "-Dkeycloak.migration.action=import -Dkeycloak.migration.provider=dir -Dkeycloak.migration.dir=/import -Dkeycloak.migration.strategy=IGNORE_EXISTING"
|
||||||
volumes:
|
volumes:
|
||||||
- ./import:/import
|
- ./import:/import
|
||||||
- ./api-key-module/target/deploy:/opt/jboss/keycloak/standalone/deployments/
|
- ./api-key-module/target/deploy:/opt/keycloak/providers/
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
|
command: ["start"]
|
||||||
dashboard-service:
|
dashboard-service:
|
||||||
build: dashboard-service
|
build: dashboard-service
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -192,31 +192,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policies": [
|
"policies": [],
|
||||||
{
|
|
||||||
"id": "6fc4ef68-6935-4bc1-b1fe-9b7109e43fe1",
|
|
||||||
"name": "Default Policy",
|
|
||||||
"description": "A policy that grants access only for users within this realm",
|
|
||||||
"type": "js",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "AFFIRMATIVE",
|
|
||||||
"config": {
|
|
||||||
"code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "54f768bd-be40-4b44-acb5-9218d5fd3e2b",
|
|
||||||
"name": "Default Permission",
|
|
||||||
"description": "A permission that applies to the default resource type",
|
|
||||||
"type": "resource",
|
|
||||||
"logic": "POSITIVE",
|
|
||||||
"decisionStrategy": "UNANIMOUS",
|
|
||||||
"config": {
|
|
||||||
"defaultResourceType": "urn:dashboard-client:resources:default",
|
|
||||||
"applyPolicies": "[\"Default Policy\"]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"scopes": []
|
"scopes": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue