Powerful powerful powerful. This is what filters are.
Let's say you want to implement authorization in your application. Only users authorized should be able to access a certain route and controller. Otherwise they should get an error page.
There are two principal ways how to add filters to your controller:
Option 1 is to add filters in the Routes file:
router.GET().route("/index").filters(SecureFilter.class).with(AppController::index);
Option 2 is to annotate our controller method with a @FilterWith
annotation:
@FilterWith(SecureFilter.class) // Only let authorized users execute the controller method public Result secureIndex() { /// do something }
Ninja will execute the filter before the method secureIndex(…)
will be called.
The SecureFilter.class
itself looks like:
public class SecureFilter implements Filter { /** If a username is saved we assume the session is valid */ public final String USERNAME = "username"; @Override public Result filter(FilterChain chain, Context context) { // if we got no cookies we break: if (context.getSession() == null || context.getSession().get(USERNAME) == null) { return Results.forbidden().html().template("/views/forbidden403.ftl.html"); } else { return chain.next(context); } } }
SecureFilter
looks into the session and tries to get a
variable called “username”. If it can do so
we can assume the user has been authenticated by us. Please refer to the sessions
section for more information why that is the case).
The filter then simply calls the next filer in the chain.
return chain.next(context);
However - if the variable username is not there we will immediately break the filter chain and will never call the method itself. It instead will return a forbidden status code and render a forbidden view.
return Results.forbidden().html().template("/views/forbidden403.ftl.html");
You can define filters that run sequentially either in the Routes file …
router.GET().route("/index").filters(LoggerFilter.class, TeaPotFilter.class).with(AppController::index);
… or by specifying them as annotation:
@FilterWith({ LoggerFilter.class, TeaPotFilter.class}) public Result teapot(Context context) { // do something }
This method will first call the LoggerFilter and then the TeaPotFilter. Each of the individual filters can break the chain or even alter the result and the context.
You can put @FilterWith
annotations on method level, but also on class level.
If you use @FilterWith
on class level all methods of this class will
be filtered.
Sometimes it is also useful to have a BaseController
that is annotated with
@FilterWith
. Other controllers extending BaseContoller
will automatically
inherit @FilterWith
.
Sometimes it's needed to run filters for every request reaching your application.
This is possible by creating a class Filters in package conf that implements the interface ApplicationFilters:
package conf; import java.util.List; import ninja.application.ApplicationFilters; import ninja.Filter; public class Filters implements ApplicationFilters { @Override public void addFilters(List<Class<? extends Filter>> filters) { filters.add(SecureFilter.class); } }
You can override these global filters for individual routes in the Routes file:
router.GET().route("/index") .globalFilters(SecureFilter.class, LoggerFilter.class) .with(AppController::index);
The most common filter examples for Ninja demonstrate how to use filters to set session values. However, non-session values can also be created in filters and passed onto other filters or your controller.
The context
parameter included with the filter
method
supports setting arbitrary attributes (values) for the current request.
public class ExampleFilter implements Filter { @Override public Result filter(FilterChain chain, Context context) { context.setAttribute("foo", "bar"); return chain.next(context); } }
In another filter or your controller, you can fetch the value of “foo” by getting the attribute.
public MyController { @FilterWith(ExampleFilter.class) public Result exampleMethod(Context context) { String foo = context.getAttribute("foo"); // foo will equal "bar" } }
Alternatively, an even more powerful design pattern is to write your own
ArgumentExtractor
that extracts the attribute value and have it
automatically injected into your controller. Visit the argument extractors
page for more info.
Ninja ships with an implementation of HTTP Basic Authentication. You need to bind a
UsernamePasswordValidator
in your conf.Modules
class and then annotate your secured
controllers or controller methods with @FilterWith(BasicAuthFilter)
.
public class Module extends AbstractModule { @Override protected void configure() { // bind a UsernamePasswordValidator bind(UsernamePasswordValidator.class).toInstance(new UsernamePasswordValidator() { @Override public boolean validateCredentials(String username, String password) { return "user".equals(username) && "password".equals(password); } }); } } public MyController { @FilterWith(BasicAuthFilter.class) public Result secureMethod(Context context) { // do something } }