Security
You can secure your application or only some parts using a filter (a RouteHandler). Remember that routes are matched in the order they are added/defined so put your security filter in front of regular routes (regular routes are endpoint routes for a request).
Simple
I will show you a simple implementation for a security filter.
// authentication filter
GET("/contact.*", routeContext -> {
if (routeContext.getSession("username") == null) {
routeContext.setSession("originalDestination", routeContext.getRequest().getContextUriWithQuery());
routeContext.redirect("/login");
} else {
routeContext.next();
}
});
// show contacts page
GET("/contacts", routeContext -> routeContext.render("contacts"));
// show contact page for the contact with id specified as path parameter
GET("/contact/{id}", routeContext -> {
int id = routeContext.getParameter("id").toInt(0);
Contact contact = (id > 0) ? contactService.getContact(id) : new Contact();
routeContext.setLocal("contact", contact);
routeContext.render("contact")
});
// show login page
GET("/login", routeContext -> response.render("login"));
// process login submit
POST("/login", routeContext -> {
String username = routeContext.getParameter("username").toString();
String password = routeContext.getParameter("password").toString();
if (authenticate(username, password)) {
String originalDestination = routeContext.removeSession("originalDestination");
routeContext.resetSession();
routeContext.setSession("username", username);
routeContext.redirect(originalDestination != null ? originalDestination : "/contacts");
} else {
routeContext.flashError("Authentication failed");
routeContext.redirect("/login");
}
});
// a dump implementation for authenticate method
private boolean authenticate(String username, String password) {
return !username.isEmpty() && !password.isEmpty();
}
The content for login (freemarker engine) can be:
<html>
<head>
<title>Login</title>
</head>
<body>
<#if flash.hasError()>
${flash.getError()}
</#if>
<form method="post" action="/login">
<input placeholder="Username" name="username">
<input placeholder="Password" name="password" type="password">
<input type="submit" value="Login">
</form>
</body>
</html>
In above code I want to protect all pages (contacts, contact) for the Contact domain entity.
The authentication tests to see if the ‘username’ attribute is present in the session object. If ‘username’ is present
than call the regular route with routeContext.next()
else redirect to the login page.
I added originalDestination
attribute because after authentication process I want to continue with the original destination (original url).
PAC4J integration
Since version 1.7.0
, Pippo comes with a new module, pippo-pac4j, that add PAC4J support in Pippo.
In few words, PAC4J is a Java security engine that supports most authentication mechanism (OAuth, SAML, CAS, LDAP, SQL, …) and
authorization mechanism (Roles/permissions, Anonymous/remember me/(fully) authenticated, CORS, CSRF, …).
The integration with PAC4J in Pippo is easy and straightforward.
The pippo-pac4j
module comes with three components (routes/filters):
Pac4jSecurityHandler
(before filter) that protects an URLPac4jLogoutHandler
that handles the (application + identity provider) logout processPac4jCallbackHandler
that finishes the login process for an indirect client
Below, I present you some snippet code to have a more clear image about this integration:
public class DemoApplication extends Application
{
@Override
protected void onInit()
{
// PAC4J config
Config config = getPac4jConfig();
// authentication filter
ANY(securePaths(), new Pac4jSecurityHandler(config, "FormClient"));
// login
GET("/login", routeContext -> {
String error = routeContext.getParameter("error").toString();
if (error != null)
{
// and error from PAC4J authentication
routeContext.flashError(getMessages().get("login.invalid", routeContext));
routeContext.redirect("login");
}
else
{
// render "login" template (it's a form)
routeContext.render("login");
}
});
Pac4jCallbackHandler callbackHandler = new Pac4jCallbackHandler(config);
callbackHandler.setRenewSession(false);
POST("/login", callbackHandler);
// logout
Pac4jLogoutHandler logoutHandler = new Pac4jLogoutHandler(config, "/");
logoutHandler.setDestroySession(true);
GET("/logout", logoutHandler);
// OTHER BUSSINES ROUTES
}
@SuppressWarnings("unchecked")
private static Optional<CommonProfile> getUserProfile(RouteContext routeContext)
{
PippoWebContext webContext = new PippoWebContext(routeContext);
ProfileManager manager = new ProfileManager(webContext);
return manager.get(true);
}
}
First of all you must create a PAC4J’s config.
A possible solution is to create a static getPac4jConfig
method with an implementation like:
private static Config getPac4jConfig() {
// create a client (form based)
FormClient formClient = new FormClient("http://localhost:8888/login", myUsernamePasswordAuthenticator);
// create the config object
Config config = new Config("http://localhost:8888/login", formClient);
// set http action handler
config.setHttpActionAdapter(PippoNopHttpActionAdapter.INSTANCE);
return config;
}
Other solution is to define all settings related to PAC4J in your application.properties
and to use SettingsConfigFactory
class:
Config config = new SettingsConfigFactory(getPippoSettings()).build();
On GET /login
route is rendered the login form. The form contains fields like username and password, an a POST /login
action.
If you want to see a more complex example please see the pippo-demo-pac4j module.
A possible integration with LDAP/AP is presented here.
Cross-Site Request Forgery (CSRF) Protection
Pippo includes a simple CSRF handler which will automatically generate a CSRF token on GET requests, if there is no token in the current session, and verify that POST requests include the session’s CSRF token.
Using this handler is straight-forward.
1. Add an ANY
filter for the protected path expression with the CSRFHandler
.
// add a CSRF token generator and validator
ANY("/books.*", new CSRFHandler());
2. Add a _csrf_token
/ ${csrfToken}
hidden input value on all forms that are POSTed to this protected path expression
<html>
<body>
<form method="post" action="/books/5/rename">
<input type="hidden" name="_csrf_token" value="${csrfToken}">
<input placeholder="Enter a new book title" name="bookTitle">
<input type="submit" value="Rename">
</form>
</body>
</html>