views:

910

answers:

3

Hi all,

I have the following situation: my application's authorization mechanism is implemented using Spring security. The central class implements AccessDecisionManager and uses voters (each of which implements AccessDecisionVoter) to decide whether to grant access to some method or not. The algorithm that tallies the votes is custom:

public class PermissionManagerImpl extends AbstractAccessDecisionManager {

    public void decide(
            Authentication authentication,
            Object object,
            ConfigAttributeDefinition config) throws AccessDeniedException {
        Iterator<?> iter = getDecisionVoters().iterator();
        boolean wasDenied = false;

        while (iter.hasNext()) {
            AccessDecisionVoter voter = (AccessDecisionVoter) iter.next();
            int result = voter.vote(authentication, object, config);

            switch (result) {
                // Some tallying calculations
            }
        }

        if (wasDenied) {
            throw new AccessDeniedException("Access is denied");
        }               
    }

}

Upon denying an access to some method, the client of the application is interested in obtaining an informative exception that specifies exactly why the access is denied. This implies passing some information from voters to the decision manager. Unfortunately, the only information the standard AccessDecisionVoter passes back to the decision manager is one of the possible return values (ACCESS_GRANTED, ACCESS_ABSTAIN or ACCESS_DENIED).

What is the best way to do it?

Thanks.

+2  A: 

Well, the AccesssDecisionVoter interface actually returns an int in this situation. Granted, the built-in voter implementations always only return one of the three constants you mentioned (and these are what the standard access decision managers check for), but then they don't really have anything extra to return - the RoleVoter for instance will deny access if and only if the principal doesn't have the required role.

Since you're using your own implementations both of the voters and the access decision manager, you have several options available as I see it:

  1. Return other values of integers as some form of error code; treat ACCESS_GRANTED, ACCESS_ABSTAIN and ACCESS_DENIED as their typical values, but treat any other integer as "access denied" with an error code. Ideally have a lookup table of error codes available - essentially a poor man's enum.
  2. Within your voter, return ACCESS_DENIED as usual, and set some publically accessible property (either on the voter object itself or perhaps some statically-accessible field) with the error reason. In your manager, if you get access denied from your custom voter, check the property to get the details.
  3. As above, set an error property within the voter; but ensure that the instance of Authentication being passed in is one of your own custom subclasses that provides a good location to set/retrieve this information.
  4. Throw an AccessDeniedException (or suitable subclass) from within your voter itself. This is not ideal as it presupposes the logic in the access decision manager; but you could either let this bubble straight up, or if needed catch it within the manager (a custom subclass would definitely be good for this) and rethrow if access really is denied (something similar to what the ProviderManager class does with its lastException variable).

None of these sticks out as the obviously correct and elegant answer, but you should be able to get something workable from whichever one is most appropriate. Since there is no explicit support within the voter framework for communicating reasons (it's a straight boolean response fundamentally) I don't think you can do much better.

Andrzej Doyle
A: 

Can't you implement AccessDecisionManager directly, without using the voters? You can then throw an AccessDeniedException with the correct information. Maybe RoleVoters are not the right abstraction to use in your case.

eljenso
+1  A: 

Thanks for people who answered.

I think I have found a quite elegant way to do what I wanted and still use the standard voters API. The 2nd parameter to the vote method of AccessDecisionVoter is the secured object. I can create a contract between the decision manager and the voters, that this object is of a specific class/interface that is a wrapper, through which the original secured object can be fetched and also additional information can be added by the voters that deny the access.

I saw a pattern like this in other frameworks as well. This solution has the following advantages over other possible solutions:

  • The voters can remain stateless, so they can be singletons
  • The standard interface of the AccessDecisionVoter is used and no new return values are added
  • The additional information is saved in an object that is discarded automatically because no one uses it after the AbstactDecisionManager's decide method, so no cleanup code is required

Cheers.

Stas