Library providing Keycloak Security for KumuluzEE framework
Library is compatible with Java 11+ and Keycloak 7.0.0+
Import library in your project:
<dependency>
<groupId>com.mjamsek.auth</groupId>
<artifactId>kee-auth</artifactId>
<version>${kee-auth.version}</version>
</dependency>
You must also provide Kecloak Jetty adapter, matching version of your Keycloak server:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty94-adapter</artifactId>
<version>${keycloak.version}</version>
</dependency>
Provide configuration values in config.yml
:
keycloak:
# Mandatory options:
realm: keycloak-realm
auth-server-url: https://keycloak.example.com/auth
client-id: keycloak-client
# Optional options
auth:
# Confidential clients need this to perform service calls
client-secret: <client_secret>
# Leeway when verifying token (default 1000)
token-leeway: 1000
# Provide public certificate for token verification with RS265.
# If it is not provided, it will be fetched from Keycloak's well-known endpoint.
cert: <cert>
For Keycloak client of type bearer only you only need to provide mandatory options (realm
, auth-server-url
and client-id
).
If you need to perform service calls to Keycloak or want to setup service as confidential client, you also need to provide client-secret
option.
To enable security in resource class, you must annotate it with @SecureResource
.
Then you can annotate methods in this class with appropriate annotations.
You can also put annotations on class. This means that non-annotated methods
will take class-based access level.
// enable security in this class
@SecureResource
// all methods need user to be authenticated (optional, you can put annotations on method only)
@AuthenticatedAllowed
@RequestScoped
@Path("/customers")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class SampleResource {
@GET
// only admins or salesmen can retrieve list of customers
@RealmRolesAllowed({"salesman", "admin"})
public Response getCustomers() {
// ...
return Response.ok(customers).build();
}
@GET
// This method uses class annotated access level - authentication only
public Response getCustomerDetails() {
// ...
return Response.ok(customerDetails).build();
}
@POST
// This method overrides class based annotation and is public - no authentication required
@PublicResource
public Response notifyCustomer() {
// ...
return Response.ok().build();
}
}
@AuthenticatedAllowed
: to access this method a user (any valid user) must present valid JWT@RolesAllowed({"dev"})
: to access this method a user must have role 'dev' (either in realm or on any client)@RealmRolesAllowed({"dev"})
: to access this method a user must have realm role 'dev'@ClientRolesAllowed(client = "keycloak-client", roles = {"dev"})
: to access this method a user must have client role 'dev' on a client 'keycloak-client'.
If you want to expose single method in otherwise protected resource class you
can use @PublicResource
annotation on method, you want to make public.
You can retrieve data about user trying to access endpoint by injecting AuthContext
object:
@Inject
private AuthContext authContext;
Alternatively, you can also retrieve raw token:
@Inject
@Token
private String rawToken;
In unsecured (public) endpoints, authContext will not be available. Therefore, it is good practice to check if user is authenticated before using its methods:
if (authContext.isAuthenticated()) {
// ...
}
Auth context provides following data:
- user id (token subject)
- username
- realm roles
- client roles
- scopes
- authenticated flag
- other claims from token
- raw token
Library also provides a client to perform service calls to keycloak server.
To use it, configuration key keycloak.auth.client-secret
must be provided.
Additionally, configured client must be confidential and service account
must be enabled (with appropriate roles assigned).
When you have configured service properly, you can call keycloak
using KeycloakClient
class:
KeycloakClient.callKeycloak((token) -> {
// perform http call to keycloak using token variable as credential
// note that provided token belongs to service account
});
callKeycloak
method accepts lambda function with one string parameter.
This parameter is set by keycloak client to service token, which it is able
to retrieve on its own using client secret we provided.
Library requires kumuluzee-rest-client
dependency to be provided at runtime.
It is therefore very advisable that you use rest client yourself when using
callKeycloak
function.
// KeycloakAPI.java
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public interface KeycloakAPI {
@GET
@Path("/admin/realms/{realm}/users")
List<Account> getAccounts(
@PathParam("realm") String realm,
@HeaderParam("Authorization") String authorizationHeader
);
}
// AccountService.java
public class AccountService {
public List<Account> getAccountsFromKeycloak() {
String realm = ConfigurationUtil.getInstance().get("keycloak.realm").get();
String keycloakUrl = ConfigurationUtil.getInstance().get("keycloak.auth-server-url").get();
KeycloakAPI api = RestClientBuilder
.newBuilder()
.baseUri(URI.create(keycloakUrl))
.build(KeycloakAPI.class);
List<Account> accounts = KeycloakClient.callKeycloak((token) -> {
return api.getAccounts(realm, "Bearer " + token);
});
return accounts;
}
}
Samples can be found on this page
When using KumuluzEE MP Rest Client if server returns 401 Jetty will throw ProcessingException, due to which response is not processed by registered mappers. Read for workaround.
Changes can be viewed on releases page on GitHub.
MIT