public class RatpackPac4j extends Object
Pac4j support many different authentication providers, such as external sources like GitHub, Twitter, Facebook etc., as well as proprietary local authentication sources.
The authenticator(Client[])
method provides a handler that implements the authentication process,
and is required in all apps wanting to use authentication.
The requireAuth(Class, Authorizer...)
method provides a handler that acts like a filter, ensuring that the user is authenticated for all requests.
This can be used for requiring authentication for all requests starting with a particular request path for example.
The userProfile(Context)
, login(Context, Class)
and logout(Context)
methods provide programmatic authentication mechanisms.
Modifier and Type | Class and Description |
---|---|
static interface |
RatpackPac4j.ClientsProvider
Provides the set of Pac4j
clients . |
Modifier and Type | Field and Description |
---|---|
static String |
DEFAULT_AUTHENTICATOR_PATH
The default path to the authenticator, "authenticator", used by
authenticator(Client[]) . |
Modifier and Type | Method and Description |
---|---|
static Handler |
authenticator(org.pac4j.core.client.Client<?,?>... clients)
|
static Handler |
authenticator(String path,
org.pac4j.core.client.Client<?,?>... clients)
Creates a handler that implements authentication when the request path matches, and makes a Pac4j
Clients available to downstream handlers otherwise. |
static Handler |
authenticator(String path,
RatpackPac4j.ClientsProvider clientsProvider)
Creates a handler that implements authentication when the request path matches, and makes a Pac4j
Clients available to downstream handlers otherwise. |
static <C extends org.pac4j.core.credentials.Credentials,U extends org.pac4j.core.profile.UserProfile> |
login(Context ctx,
Class<? extends org.pac4j.core.client.Client<C,U>> clientType)
Logs the user in by redirecting to the authenticator, or provides the user profile if already logged in.
|
static Operation |
logout(Context ctx)
Logs out the current user, removing their profile from the session.
|
static <C extends org.pac4j.core.credentials.Credentials,U extends org.pac4j.core.profile.UserProfile> |
requireAuth(Class<? extends org.pac4j.core.client.Client<C,U>> clientType,
org.pac4j.core.authorization.Authorizer<? super U>... authorizers)
An authentication and authorization “filter”.
|
static Promise<Optional<org.pac4j.core.profile.UserProfile>> |
userProfile(Context ctx)
Obtains the logged in user's profile, if the user is logged in.
|
static <T extends org.pac4j.core.profile.UserProfile> |
userProfile(Context ctx,
Class<T> type)
Obtains the logged in user's profile, of the given type, if the user is logged in.
|
public static final String DEFAULT_AUTHENTICATOR_PATH
authenticator(Client[])
.public static Handler authenticator(org.pac4j.core.client.Client<?,?>... clients)
clients
- the supported auth clientspublic static Handler authenticator(String path, org.pac4j.core.client.Client<?,?>... clients)
Clients
available to downstream handlers otherwise.
This methods performs the same function as authenticator(String, ClientsProvider)
,
but is more convenient to use when the Client
instances do not depend on the request environment.
path
- the path to bind the authenticator to (relative to the current request path binding)clients
- the supported authentication clientspublic static Handler authenticator(String path, RatpackPac4j.ClientsProvider clientsProvider)
Clients
available to downstream handlers otherwise.
This handler MUST be BEFORE any code in the handler pipeline that tries to identify the user, such as a requireAuth(java.lang.Class<? extends org.pac4j.core.client.Client<C, U>>, org.pac4j.core.authorization.Authorizer<? super U>...)
handler in the pipeline.
It should be added to the handler chain via the Chain.all(Handler)
.
That is, it should not be added with Chain.get(Handler)
or any method that filters based on request method.
It is common for this handler to be one of the first handlers in the pipeline.
This handler performs two different functions, based on whether the given path matches the PathBinding.getPastBinding()
component of the current path binding.
If the path matches, the handler will attempt authentication, which may involve redirecting to an external auth provider, which may then redirect back to this handler.
If authentication is successful, the UserProfile
of the authenticated user will be placed into the session.
The user will then be redirected back to the URL that initiated the authentication.
If the path does not match, the handler will push an instance of Clients
into the context registry and pass control downstream.
The Clients
instance will be retrieved downstream by any requireAuth(Class, Authorizer...)
handler (or use of login(Context, Class)
.
path
- the path to bind the authenticator to (relative to the current request path binding)clientsProvider
- the provider of authentication clients@SafeVarargs public static <C extends org.pac4j.core.credentials.Credentials,U extends org.pac4j.core.profile.UserProfile> Handler requireAuth(Class<? extends org.pac4j.core.client.Client<C,U>> clientType, org.pac4j.core.authorization.Authorizer<? super U>... authorizers)
This handler can be used to ensure that a user profile is available for all downstream handlers.
If there is no user profile present in the session (i.e. user not logged in), authentication will be initiated based on the given client type (i.e. redirect to the authenticator(Client[])
handler).
If there is a UserProfile
present in the session, this handler will push the user profile into the context registry before delegating downstream.
If there is a UserProfile
present in the context registry, this handler will simply delegate downstream.
If there is a UserProfile
, each of the given authorizers will be tested in turn and all must return true.
If so, control will flow to the next handler.
Otherwise, a 403
client error
will be issued.
This handler requires a Clients
instance available in the context registry.
As such, this handler should be downstream of the authenticator(Client[])
handler.
import org.pac4j.core.profile.UserProfile;
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
import ratpack.guice.Guice;
import ratpack.pac4j.RatpackPac4j;
import ratpack.session.SessionModule;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.registry(Guice.registry(b -> b.module(SessionModule.class)))
.handlers(c -> c
.all(RatpackPac4j.authenticator(new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator())))
.get("logout", ctx -> RatpackPac4j.logout(ctx).then(() -> ctx.render("logged out")))
.prefix("require-authn", a -> a
.all(RatpackPac4j.requireAuth(IndirectBasicAuthClient.class))
.get(ctx -> ctx.render("Hello " + ctx.get(UserProfile.class).getId()))
)
.prefix("require-authz", a -> a
.all(RatpackPac4j.requireAuth(IndirectBasicAuthClient.class, (ctx, profile) -> { return "special-user".equals(profile.getId()); }))
.get(ctx -> ctx.render("Hello " + ctx.get(UserProfile.class).getId()))
)
.get(ctx -> ctx.render("no auth required"))
)
).test(httpClient -> {
httpClient.requestSpec(r -> r.redirects(1));
assertEquals("no auth required", httpClient.getText());
assertEquals(401, httpClient.get("require-authn").getStatusCode());
assertEquals("Hello user", httpClient.requestSpec(r -> r.basicAuth("user", "user")).getText("require-authn"));
assertEquals(403, httpClient.get("require-authz").getStatusCode());
assertEquals("logged out", httpClient.getText("logout"));
httpClient.resetRequest();
assertEquals(401, httpClient.get("require-authz").getStatusCode());
assertEquals("Hello special-user", httpClient.requestSpec(r -> r.basicAuth("special-user", "special-user")).getText("require-authz"));
});
}
}
clientType
- the client type to use to authenticate with if requiredauthorizers
- the authorizers to check authorizationspublic static <C extends org.pac4j.core.credentials.Credentials,U extends org.pac4j.core.profile.UserProfile> Promise<U> login(Context ctx, Class<? extends org.pac4j.core.client.Client<C,U>> clientType)
This method can be used to programmatically initiate a log in, if required.
If the user is already logged in, the user profile will be provided via the returned promise.
If the user is not already logged in, the promise will not be fulfilled and the user will be redirected to the authenticator.
As such, like requireAuth(Class, Authorizer...)
, this can only be used downstream of the authenticator(Client[])
handler.
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
import ratpack.guice.Guice;
import ratpack.http.client.ReceivedResponse;
import ratpack.pac4j.RatpackPac4j;
import ratpack.session.SessionModule;
import ratpack.test.embed.EmbeddedApp;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.registry(Guice.registry(b -> b.module(SessionModule.class)))
.handlers(c -> c
.all(RatpackPac4j.authenticator(new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator())))
.get("auth", ctx -> RatpackPac4j.login(ctx, IndirectBasicAuthClient.class).then(p -> ctx.redirect("/")))
.get(ctx ->
RatpackPac4j.userProfile(ctx)
.route(Optional::isPresent, p -> ctx.render("Hello " + p.get().getId()))
.then(p -> ctx.render("not authenticated"))
)
)
).test(httpClient -> {
// user is not authenticated
assertEquals("not authenticated", httpClient.getText());
// authenticate…
ReceivedResponse response = httpClient.requestSpec(r -> r.basicAuth("user", "user")).get("auth");
// authenticated (redirected to /)
assertEquals("Hello user", response.getBody().getText());
});
}
}
ctx
- the handling contextclientType
- the client type to authenticate withpublic static Promise<Optional<org.pac4j.core.profile.UserProfile>> userProfile(Context ctx)
The promised optional will be empty if the user is not authenticated.
This method should be used if the user may have been authenticated.
That is, when the the need for the profile is not downstream of an requireAuth(Class, Authorizer...)
handler,
as the auth handler puts the profile into the context registry for easy retrieval.
This method returns a promise as it will attempt to load the profile from the session if it isn't already in the context registry.
import io.netty.handler.codec.http.HttpHeaderNames;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
import ratpack.guice.Guice;
import ratpack.http.client.ReceivedResponse;
import ratpack.pac4j.RatpackPac4j;
import ratpack.session.SessionModule;
import ratpack.test.embed.EmbeddedApp;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.registry(Guice.registry(b -> b.module(SessionModule.class)))
.handlers(c -> c
.all(RatpackPac4j.authenticator(new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator())))
.prefix("auth", a -> a
.all(RatpackPac4j.requireAuth(IndirectBasicAuthClient.class))
.get(ctx -> {
ctx.render("Hello " + ctx.get(UserProfile.class).getId());
})
)
.get("no-auth", ctx -> {
RatpackPac4j.userProfile(ctx)
.route(Optional::isPresent, p -> ctx.render("Hello " + p.get().getId()))
.then(p -> ctx.render("not authenticated"));
})
)
).test(httpClient -> {
// User is not authenticated
assertEquals("not authenticated", httpClient.getText("no-auth"));
// Authenticate…
ReceivedResponse response = httpClient.requestSpec(r -> r.redirects(0)).get("auth");
assertEquals(302, response.getStatusCode());
String redirectTo = response.getHeaders().get(HttpHeaderNames.LOCATION);
assertEquals(401, httpClient.get(redirectTo).getStatusCode());
response = httpClient.requestSpec(r -> r
.basicAuth("user", "user")
.redirects(0)
).post(redirectTo);
assertEquals(302, response.getStatusCode());
redirectTo = response.getHeaders().get(HttpHeaderNames.LOCATION);
assertTrue(redirectTo.endsWith("/auth"));
assertEquals("Hello user", httpClient.getText(redirectTo));
// User is now authenticated
assertEquals("Hello user", httpClient.getText("no-auth"));
});
}
}
ctx
- the handling contextuserProfile(Context, Class)
public static <T extends org.pac4j.core.profile.UserProfile> Promise<Optional<T>> userProfile(Context ctx, Class<T> type)
The promised optional will be empty if the user is not authenticated.
If there exists a UserProfile
for the current user but it is not compatible with the requested type,
the returned promise will be a failure with a ClassCastException
.
This method should be used if the user may have been authenticated.
That is, when the the need for the profile is not downstream of an requireAuth(Class, Authorizer...)
handler,
as the auth handler puts the profile into the context registry for easy retrieval.
This method returns a promise as it will attempt to load the profile from the session if it isn't already in the context registry.
T
- the type of the user profilectx
- the handling contexttype
- the type of the user profileuserProfile(Context)
public static Operation logout(Context ctx)
The returned operation simply removes the profile from the session, regardless of whether it's actually there or not.
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
import ratpack.guice.Guice;
import ratpack.http.client.ReceivedResponse;
import ratpack.pac4j.RatpackPac4j;
import ratpack.session.SessionModule;
import ratpack.test.embed.EmbeddedApp;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.registry(Guice.registry(b -> b.module(SessionModule.class)))
.handlers(c -> c
.all(RatpackPac4j.authenticator(new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator())))
.get("auth", ctx -> RatpackPac4j.login(ctx, IndirectBasicAuthClient.class).then(p -> ctx.redirect("/")))
.get(ctx ->
RatpackPac4j.userProfile(ctx)
.route(Optional::isPresent, p -> ctx.render("Hello " + p.get().getId()))
.then(p -> ctx.render("not authenticated"))
)
.get("logout", ctx ->
RatpackPac4j.logout(ctx).then(() -> ctx.redirect("/"))
)
)
).test(httpClient -> {
// user is not authenticated
assertEquals("not authenticated", httpClient.getText());
// authenticate…
ReceivedResponse response = httpClient.requestSpec(r -> r.basicAuth("user", "user")).get("auth");
// authenticated (redirected to /)
assertEquals("Hello user", response.getBody().getText());
// logout (redirected to /)
assertEquals("not authenticated", httpClient.getText("logout"));
});
}
}
ctx
- the handling context