Protecting Service Methods with Spring Security Annotations

Spring Security is typically used to protect Web Applications by restricting access to URLs based on a user role. However, it can also be used to secure methods and classes so that coding or configuration errors do not allow a back door into restricted data. This builds security deep into the system without cluttering the code. It also allows additional flexibility such as allowing users to access only information relevant to them and not to other users’ information.

All of the following code is available for download as part of the Spanners demo. As well as demonstrating Spring Security, this application also demonstrates various features and techniques illustrated in previous posts. The Spring Security demonstration was done in the spanners-dao and spanners-struts projects only. The other projects in the demo are not secured in this way.

Securing the web application

All HTTP requests received by a web application can be secured using the Spring Security Namespace. This allows simple role based user authorization to individual pages of a web application.

This is a good start but is fairly inflexible, hard to unit test and prone to mistakes. While it’s worth securing applications and individual pages in this way, additional checks should be added.

Securing classes and methods

Any class or method can be protected with Spring Security using either AOP interceptors or expression based annotations on the class or method. I’ve chosen to use annotations as I think it improves readability if the access rules are part of the interface definition.

A simple annotated interface looks like this:

A couple of notes on this:

  1. The annotations belong in the interface, not the implementation. Any implementation of this interface, be it a concrete class, a Spring generated proxy or a test stub should be protected. Also, we only want to secure the public contract methods of the class. If you’re trying to secure private methods, you’re doing something wrong.
  2. The class is annotated with @PreAuthorize(“hasRole(‘ROLE_EXCLUDE_ALL’)”). This is the default rule that will be applied to all methods unless they have their own @PreAuthorize annotation. This means that if we forget to annotate a method, it will default to ‘exclude all’. This is generally good security practice – exclude access to all unless explicitly allowed.

These annotations by themselves do nothing unless we tell Spring Security to recognize them in our security configuration:

 Securing by parameterized expression

Annotation based security allows much greater flexibility in its rules than just simple role based access control (RBAC). It allows rules to be built using the Spring Expression Language (SpEL) and interpreted using customizable evaluators. For example, I want my spanner mutator operations (create, update, delete) to be available only to the owner of the spanner being modified. This goes beyond role based access control unless I set up roles per spanner. This is an access control list (ACL) where each spanner has a list of users (containing just one user) that may modify it.

The mutator methods of my interface have security annotations set up as follows:

The hasPermission expression allows us to use the Spring ACL module to evaluate permissions. Note that we refer to the spanner parameter in the expression as #spanner.

The built in ACL module is a little heavy for what we need so I’ve chosen to replace the Spring provided implementation with my own permission evaluator.

The hasPermission method checks the ‘owner’ attribute of the given spanner against the currently logged in user (authentication.getName()) and returns true if and only if they match.

This is wired up to the method based security as follows:

Testing the permissions

It’s far simpler to unit test security on Spring beans than it is to test on HTTP requests. After stubbing the Spring security config (see¬†spring-test-security.xml in the demo source), we can easily test every method call for a permission exception using the googlecode catch-exception library:

In this test example, I’ve injected the ‘real’ SpannersDAOImpl implementation of SpannersDAO but I could just as well create a stub or mock of SpannersDAO for this test. Remember, I’m testing the annotations on the interface, not the implementation. When testing the security, I can use any implementation of SpannersDAO that I like. This offers some additional flexibility in how we set up our tests.

Putting it all together

When I use this annotated interface and security configuration in my web application, I’m now asked to login to view the application:

Login page showing username and password fields

After logging in, I can navigate the site as normal but if I try to do something I’m not allowed to do, like update another user’s spanner, an exception is thrown:

Access denied message showing stack trace

Note that the exception is thrown on a call to a Spring proxy for the SpannersDAO. Note also that the SpannersDAOImpl is not in this stack trace – the request is intercepted before it even reaches the implementation class. Of course, this exception (and indeed all exceptions) should be caught and handled by my web application and stack traces should never be visible to the end user.


  • sb
    October 1, 2013 - 1:37 pm | Permalink

    the preauthroize annaotion references paramter name, this requires compilation in debug mode ? Which is a bit of show stopper for me at least,

  • October 1, 2013 - 6:32 pm | Permalink

    Thanks for pointing that out, sb. That’s the first I’ve heard of this issue. Unfortunately I can’t offer advice on workarounds for this as I’ve always compiled with debug info.

  • Pingback: SAML based Single Sign On (SSO) in Spring Security applications « Don't Panic!

  • Pingback: Testing with mock users in Spring / Spring MVC « Don't Panic!

  • Venkat
    November 3, 2016 - 11:07 pm | Permalink


    I am new to Spring Security SAML integration, could you please provide any POC running example.

    Many thanks

  • Leave a Reply

    Your email address will not be published. Required fields are marked *