Fork me on GitHub

SpringSandwich

Annoted, type-safe interceptors for Spring web controllers

Annotate security or other interceptors for your Spring web controllers, much like you normally annotate routes or endpoints.

    @Before(@BeforeElement( MySecurityFilter.class ))
    @Controller
    public class MyController { 

        @RequestMapping("/helloworld")
        @ResponseBody
        public String heyworld() {
            return "hey there big n blue";
        }

    } 

To secure all the endpoints in your @Controller class, add a @Before annotation like this (you can also add it to individual methods for more granular control):

    @Component
    public class MySecurityFilter implements BeforeHandler {
        @Override
        public Flow handle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, String[] flags) throws Exception {
            //determine if this request is authorized, using daos or any other mechanism.
            //you have full access to Spring context and autowiring here
            //to halt this request and prevent execution of the controller, return Flow.HALT 
            //you may also redirect to a login page here if desired
            return Flow.CONTINUE;
        }
    } 

Then create your implementing class for MySecurityFilter and make sure it's reachable by Spring:

This has the following advantages over Spring Security:
  • Your security boundaries are easy to see in the controller file itself, just like mvc routes
  • Unlike Spring security boundaries, it is type-safe -- if you reorganize your packages, your security still works because it's defined in the file itself
  • This is subjective, but IMO for the simple yet common use case of writing code that runs before your controllers, I find this to be much simpler to use and setup.

How to set it up

1. Include it in your project's pom.xml:
    <dependencies>
        <dependency>
            <groupId>com.kastkode</groupId>
            <artifactId>springsandwich</artifactId>
            <version>[1.0.2,)</version>
        </dependency>
    </dependencies> 
2. In your application, tell Spring to pick up the library. For instance in Spring Boot you would add this annotation to your Main starting class:
    @ComponentScan(basePackages = {"com.kastkode.springsandwich.filter", "com.your-app-here.*"})
    public class Main { ... } 
(Notice that we explicitly ComponentScan our Main package as well since we're overriding Spring Boot's default scanning.)

More details on how to use SpringSandwich

A full implementation for an interceptor might look like this:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import com.kastkode.springsandwich.filter.api.BeforeHandler;
import com.kastkode.springsandwich.filter.api.Flow;

@Component
public class RestrictByRole implements BeforeHandler {

    @Override
    public Flow handle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, String[] flags) throws Exception {
        System.out.println("RestrictByRole logic executed, checking for these roles");
        if(flags != null) {
            for(String arg:flags) {
                System.out.println(arg);
            }
        }

        //or return Flow.HALT to halt this request and prevent execution of the controller
        //you may also wish to redirect to a login page here
        return Flow.CONTINUE;
    }
} 
Apply it to your controller as an annotation either at the class or the method (or both):
@Before( @BeforeElement(RestrictByRole.class)) 
You can also pass a list of strings for the interceptor to consider. Here the flags "admin" and "manager" are passed in to the RestrictByRole implementation method
@Before(
    @BeforeElement(value = RestrictByRole.class, flags = {"admin", "manager"})
) 
You can apply several interceptors in sequence like this
@Before({
    @BeforeElement(IPWhiteListCheck.class),
    @BeforeElement(LoginWall.class),
    @BeforeElement(value = RestrictByRole.class, flags = {"admin", "manager"})
}) 
There's also an After interceptor you can use
@After(
    @AfterElement(DoThisAfter.class)
) 
Many common use cases have already been addressed in premade interceptors found in com/kastkode/springsandwich/filter/coldcuts/. Consider extending them as a starting point for your interceptor

FAQ

How is SpringSandwich different from Spring AOP or Spring Security?

  1. SpringSandwich is type-safe, whereas Spring AOP and Spring Security rely on String-based annotations which cannot be tested until runtime and which do not survive refactorings. Spring AOP furthermore uses global String expressions, making it sometimes less transparent about where advice is being applied (or even worse, silently NOT applied!).
  2. SpringSandwich is vastly simpler to use. This is of course hard to prove; you'll have to just try and compare for yourself. But you may wish to have a quick look at Spring AOP's documentation for comparison.

How is SpringSandwich different from a servlet filter?

SpringSandwich has these main advantages over servlet filters:
  1. It can be directly applied via annotations, and is thus type-safe and will survive refactorings
  2. Your interceptors have full access to the spring context. For instance, it is difficult to write a standard servlet filter that uses your daos to lookup a user because typically your daos require the spring context, which won't be accessible from a servlet filter. But in SpringSandwich, you have full access -- write your regular code like anywhere else.
  3. By re-using the routing information already defined in your controller instead of redundantly defininig elsewhere the routes to which it applies, SpringSandwich facilitates DRYer software with less room for error. In other words, it's a cleaner solution!
SpringSandwich (c) Ari Kast 2016.