March 30, 2011

Spring Security by example: securing methods

This is a part of a simple Spring Security tutorial:

1. Set up and form authentication
2. User in the backend (getting logged user, authentication, testing)
3. Securing web resources
4. Securing methods
5. OpenID (login via gmail)
6. OAuth2 (login via Facebook)
7. Writing on Facebook wall with Spring Social

Securing web resources is all nice and cool, but in a well designed application it's more natural to secure methods (for example on backend facade or even domain objects). While we may get away with role-based authorization in many intranet business applications, nobody will ever handle assigning roles to users in a public, free to use Internet service. We need authorization based on rules described in our domain.

For example: there is a service AlterStory, that allows cooperative writing of stories, where one user is a director (like a movie director), deciding which chapter proposed by other authors should make it to the final story.

The method for accepting chapters, looks like this:

   
public void acceptChapterProposal(Long revisionVersion, Long chapterId, Long storyId) {
Our domain rule is, that the logged in user can call this method, only if he is the director of the story.

This will be described by an annotation like this:
   
@PreAuthorize("isAuthenticated() and hasPermission(#storyId, 'isDirector')")
public void acceptChapterProposal(Long revisionVersion, Long chapterId, Long storyId) {
…
While isAuthenticated may seem redundant in here, it's for optimization. No need to check something in the database if the current user is anonymou.

And we want to be able to write more rules like that, for example:
@PreAuthorize("isAuthenticated() and hasPermission(#profileLogin, 'isProfileOwner')") 
and
@PreAuthorize("isAuthenticated() and hasPermission(#chapterId, 'isChapterAuthor')")
The string value in @PreAuthorize annotation is written in SpEL: Spring Expression Language, which in this example allows us to use “and”, and to access method properties by giving their names (#storyId). Yes, yes, debug mode compilation is required, otherwise parameter name get erased. How bad is that?

Spring Security supports @PreAuthorize, @Secured and JSR-250, but I'll stay with @PreAuthorize in this post.

Spring will make the whole thing work thanks to Aspect Oriented Programming by creating proxies on the fly. Authorization is a typical example of where AOP should be used, just be aware of it, because if you want to secure entities (or any classes created by 'new'), you need load time or compile time weaving.

Ok, so we know how we want to use it. Let's make it happen. We need to add this to our security.xml
   
<!-- enable support for Expression-based annotations in Spring Security -->
    <global-method-security pre-post-annotations="enabled" >
        <expression-handler ref="expressionHandler"/>
    </global-method-security>

    <beans:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
        <beans:property name="permissionEvaluator" ref="permissionEvaluator"/>
    </beans:bean>
We get isAuthenticated() out of the box. How about our custom domain rules ( isDirector)? That's what permissionEvaluator is for.

The interface is simple
public interface PermissionEvaluator extends AopInfrastructureBean {
    boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
    boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);
}
Authentication is the representation of the logged user, targetDomainObject corresponds to the first parameter of hasPermission(#storyId, 'isDirector') (in this case it's storyId), and permission is the second parameter ('isDirector').

Since we want to have many custom rules, each represented by a class of it's own. Let's write an interface for a single rule and a class (our evaluator) that will choose and fire the corresponding rule. I'm not going to use the second method right now.

public interface Permission {
    boolean isAllowed(Authentication authentication, Object targetDomainObject);
}
public class StorytellingPermissionEvaluator implements PermissionEvaluator {
    private Map<String, Permission> permissionNameToPermissionMap = new HashMap<String, Permission>();

    protected StorytellingPermissionEvaluator() {}

    public StorytellingPermissionEvaluator(Map<String, Permission> permissionNameToPermissionMap) {
        notNull(permissionNameToPermissionMap);
        this.permissionNameToPermissionMap = permissionNameToPermissionMap;
    }

    @Override
    @Transactional
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        boolean hasPermission = false;
        if (canHandle(authentication, targetDomainObject, permission)) {
            hasPermission = checkPermission(authentication, targetDomainObject, (String) permission);
        }
        return hasPermission;
    }

    private boolean canHandle(Authentication authentication, Object targetDomainObject, Object permission) {
        return targetDomainObject != null && authentication != null && permission instanceof String;
    }

    private boolean checkPermission(Authentication authentication, Object targetDomainObject, String permissionKey) {
        verifyPermissionIsDefined(permissionKey);
        Permission permission = permissionNameToPermissionMap.get(permissionKey);
        return permission.isAllowed(authentication, targetDomainObject);
    }

    private void verifyPermissionIsDefined(String permissionKey) {
        if (!permissionNameToPermissionMap.containsKey(permissionKey)) {
            throw new PermissionNotDefinedException("No permission with key " + permissionKey + " is defined in " + this.getClass().toString());
        }
    }

    @Override    
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        throw new PermissionNotDefinedException("Id and Class permissions are not supperted by " + this.getClass().toString());
    }
}
And now the xml configuration:
<bean id="permissionEvaluator"
          class="pl.touk.storytelling.infrastructure.services.authorization.StorytellingPermissionEvaluator">
        <constructor-arg index="0">
            <map key-type="java.lang.String"
                 value-type="pl.touk.storytelling.infrastructure.services.authorization.permissions.Permission">
                <entry key="isDirector" value-ref="directorPermission"/>
                <entry key="isChapterAuthor" value-ref="chapterAuthorPermission"/>
                <entry key="isProfileOwner" value-ref="profileOwnerPermission"/>
                <entry key="hasPasswordChangePermission" value-ref="passwordChangePermission"/>
            </map>
        </constructor-arg>
    </bean>

    <bean id="directorPermission"
          class="pl.touk.storytelling.infrastructure.services.authorization.permissions.DirectorPermission">
        <constructor-arg index="0" ref="storyRepository"/>
    </bean>

    <bean id="chapterAuthorPermission"
          class="pl.touk.storytelling.infrastructure.services.authorization.permissions.ChapterAuthorPermission">
        <constructor-arg index="0" ref="chapterRepository"/>
    </bean>

    <bean id="profileOwnerPermission"
          class="pl.touk.storytelling.infrastructure.services.authorization.permissions.ProfileOwnerPermission"/>      

    <bean id="passwordChangePermission"
          class="pl.touk.storytelling.infrastructure.services.authorization.permissions.ChangePasswordPermission">
        <constructor-arg index="0" ref="userRepository"/>
    </bean>
And to finish the example, the logic for our rule:
public class DirectorPermission implements Permission {
    private StoryRepository storyRepository;

    public DirectorPermission(StoryRepository storyRepository) {
        this.storyRepository = storyRepository;
    }

    @Override
    public boolean isAllowed(Authentication authentication, Object targetDomainObject) {
        boolean hasPermission = false;
        if (isAuthenticated(authentication) && isStoryId(targetDomainObject)) {
            Story story = storyRepository.findStoryById((Long) targetDomainObject);
            String login = getLogin(authentication);
            if (story.isDirectedBy(login)) {
                hasPermission = true;
            }
        }
        return hasPermission;
    }

    private String getLogin(Authentication authentication) {
        Object authenticationUserDetails = authentication.getPrincipal();
        return ((AuthenticationUserDetails) authenticationUserDetails).getUsername();
    }

    private boolean isStoryId(Object targetDomainObject) {
        return targetDomainObject instanceof Long && (Long) targetDomainObject > 0;
    }

    private boolean isAuthenticated(Authentication authentication) {
        return authentication != null && authentication.getPrincipal() instanceof AuthenticationUserDetails;
    }
} 

Next: OpenID (login via gmail)

11 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Do you need the ACL Module to make this work? My method security is not working... Any of my method with @PreAuthorize annotation is not evaluated at all. Is there anything that I should do as a pre-condition to doing your example?

    ReplyDelete
  3. @Christian: Not sure what you mean by "ACL Module".

    I've just checked on a new project. On a class defined in spring IoC I have a method:

    @PreAuthorize("isAuthenticated()")
    public boolean securedMethod() {
    return true;
    }

    Assuming you have dependecies in your pom (described here: http://blog.solidcraft.eu/2011/03/spring-security-by-example-set-up-and.html) the only thing I needed to add, to make this method protected (throwing AccessDeniedException for not logged in user) was to add this to spring configuration:

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

    Nothing more.

    Maybe your @PreAuthorize annotation is on a class, which is not defined in IoC (or created by "new" without load/compile time weaving)?

    ReplyDelete
  4. I am not using Maven actually. I just use Ant Script to deploy my app to Tomcat. It should not matter, right?

    ReplyDelete
  5. It should not.
    Can you put your sources somewhere like in github for me to see?

    ReplyDelete
  6. I red your response again that i missed the part about "load/compile time weaving". I used Java configuration which therefore my beans are loaded with new keyword. I assume then that method level security will not work if that is not done?

    ReplyDelete
  7. @thundersaint: yeah, there is no magic in here.

    Securing methods works by adding aspect to your code, which means: some other code will be called before/after/instead of your method, and AFAIK there are only three options to do that:

    "Weaving: [..] can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime."
    (http://static.springsource.org/spring/docs/3.0.x/reference/aop.html)

    "At runtime" means that your classes have to be defined as beans and injected by DI (IoC) container (Spring core), because Spring AOP will give you proxies with security checks instead of your *real* classes (proxies will be either Spring implementation of your public interfaces, or Spring classes inheriting from yours).

    When you want to call "new", something has to change your bytecode to check the security before every method call. With compile time weaving, security checks are "inserted into the compiled bytecode", while with load time weaving, there is a java agent that "takes over" loading the class, and gives you a modified version instead.

    Btw. Sorry for not being precise/correct, but AOP nomenclature is a bit wicked and I'd rather have a helpful and understandable answer than a correct one.

    ReplyDelete
  8. Jakub, this tutorial is great, simple, and easy to read but it would even more helpful if you include your configuration in the matter of "load/compile time weaving" on your method security section. I really think so.

    ReplyDelete
  9. and if you want to use this without the debug compile information available

    http://lifewithcode.blogspot.co.uk/2012/04/spring-security-preauthorizehaspermissi.html

    ReplyDelete
  10. Hi, can I have multiple permission providers? One each for each modules? This is because the business logic implemented is different and complex. Is it possible to configure multiple permission providers in security config file?

    Thanks/Joe

    ReplyDelete
  11. Nice Tutorial!

    We can Refer also below

    http://www.hardik4u.com/2012/10/spring-security-p1.html

    ReplyDelete