Skip to content

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.

<http auto-config="true">
    <intercept-url pattern="/**" access="ROLE_VIEWER" />
</http>

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:

@PreAuthorize("hasRole('ROLE_EXCLUDE_ALL')")
public interface SpannersDAO {

    @PreAuthorize("hasRole('ROLE_VIEWER')")
    public Spanner get(int id);

    @PreAuthorize("hasRole('ROLE_VIEWER')")
    public List<Spanner> getAll();

    // More method definitions...
}

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:

<global-method-security pre-post-annotations="enabled">

 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:

@PreAuthorize("hasRole('ROLE_EDITOR') or hasPermission(#spanner, 'owner')")
public int create(Spanner spanner);

@PreAuthorize("hasRole('ROLE_EDITOR') or hasPermission(#spanner, 'owner')")
public void update(Spanner spanner);

@PreAuthorize("hasRole('ROLE_EDITOR') or hasPermission(#spanner, 'owner')")
public void delete(Spanner spanner);

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.

public class SpannerPermissionEvaluator implements PermissionEvaluator{

    public static final String OWNER = "owner";

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        boolean hasPermission = false;
        if (targetDomainObject instanceof Spanner && permission.toString().equals(OWNER)) {
            Spanner spanner = (Spanner)targetDomainObject;
            hasPermission = authentication.getName().equals(spanner.getOwner());
        }
        return hasPermission;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;  // Unsupported for now
    }
}

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:

<global-method-security pre-post-annotations="enabled">
    <expression-handler ref="expressionHandler"/>
</global-method-security>

<beans:bean id="expressionHandler">
    <beans:property name="permissionEvaluator" ref="spannerPermissionEvaluator"/>
</beans:bean>

<beans:bean id="spannerPermissionEvaluator"/>

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:

@Test
public void testViewerAccess() {

    // Login as viewer
    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("viewer", "password"));

    // Viewer should have access to get* methods - just call the method to check no exception is thrown
    spannersDAO.get(1);
    spannersDAO.getAll();

    // Viewer should not have access to create / update / delete
    Spanner spanner = newSpanner();
    verifyException(spannersDAO, AccessDeniedException.class).create(spanner);
    verifyException(spannersDAO, AccessDeniedException.class).update(spanner);
    verifyException(spannersDAO, AccessDeniedException.class).delete(spanner);
}

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.

Published inJava TechnologiesSecurityTestingWeb Technologies

11 Comments

  1. 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.

  2. […] object and setting it in the test’s SecurityContext. This is described in a previous post on Protecting Service Methods with Spring Security Annotations. It’s relatively straightforward to do this but it does clutter the test somewhat. We want to […]

  3. Venkat Venkat

    Hi,

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

    Many thanks

  4. […] sophisticated access control can be implemented on individual objects using expression based annotations as described in a previous post. To make this work using Java based configuration, annotate the […]

  5. Neetu Neetu

    Hi ,

    I dont want to use spring IDP rather i will have my own IDP(internal ADFS system ) for login . how can i do that with this code ? it will be great if u can help me with this information .

  6. Mikayil Mikayil

    Do you think I can provide the argument to the hasAuthority method from application.properties file, doing something like this?:

    @PreAuthorize(“hasAuthority(‘${rights.view}’)”)

    I’ve tried the above and all below, but none worked.

    @PreAuthorize(“hasAuthority(‘$rights.view’)”)

    @PreAuthorize(“hasAuthority(${rights.view})”)

    @PreAuthorize(“hasAuthority($rights.view)”)

    I get could not evaluate spring expression

    • Sorry Mikayil

      I’ve had a lot of trouble using Spring Expression Language in these annotations too. For what it’s worth, I wouldn’t expect your syntax to work. The $ placeholder syntax only works within SpEL and you usually need to use # to denote a Spring Expression. Within most Spring annotations, something like this would work:
      #{ ${rights.view}}

      Unfortunately, @PreAuthorize does not use the usual SpEL context and so this doesn’t work in the version of Spring Security I used (4.0.1.RELEASE).

      The best suggestion I can offer is that you write a PermissionEvaluator as described in this post. In my example, I’ve set ‘owner’ as a constant in the SpannerPermissionEvaluator class. You could instead inject this value using a property placeholder ${rights.view}. That would shift your properties file lookup from the @PreAuthorize expression to your Spring config (XML or Java based). You’d then use your PermissionEvaluator with

      @PreAuthorize(“hasPermission(#spanner, ‘VIEW’)”)

      When your PermissionEvaluator checks for ‘VIEW’ permission, it checks if your Principal has a GrantedAuthority with the name matching the injected ${rights.view} value.

Leave a Reply

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