Laures
Laures

Reputation: 5489

Change security-context for method call with spring-security

Currently I'm using spring security and @PreAuthorize annotations to secure method calls. Now I want to change the authentication token for a method call like the run-as authentication replacement of spring security allows me to do.

Can I configure the replacement on a per method base? Per annotation, SpEL expression.... If not, would it be possible do figure out in the runAsManager what method is called? How would I configure the security config attributes for a secured object, at all?

Upvotes: 7

Views: 4455

Answers (2)

kaqqao
kaqqao

Reputation: 15429

I've posted a detailed article on implementing Run-As in conjunction with @PreAuthorize.

1) Implement your own RunAsManager that creates the Authentication to use during method execution based on any custom logic. The example below uses a custom annotation that provides the extra role:

public class AnnotationDrivenRunAsManager extends RunAsManagerImpl {

        @Override
        public Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
            if(!(object instanceof ReflectiveMethodInvocation) || ((ReflectiveMethodInvocation)object).getMethod().getAnnotation(RunAsRole.class) == null) {
                return super.buildRunAs(authentication, object, attributes);
            }

            String roleName = ((ReflectiveMethodInvocation)object).getMethod().getAnnotation(RunAsRole.class).value();

            if (roleName == null || roleName.isEmpty()) {
                return null;
            }

            GrantedAuthority runAsAuthority = new SimpleGrantedAuthority(roleName);
            List<GrantedAuthority> newAuthorities = new ArrayList<GrantedAuthority>();
            // Add existing authorities
            newAuthorities.addAll(authentication.getAuthorities());
            // Add the new run-as authority
            newAuthorities.add(runAsAuthority);

            return new RunAsUserToken(getKey(), authentication.getPrincipal(), authentication.getCredentials(),
                    newAuthorities, authentication.getClass());
        }
    }

This implementation will look for a custom @RunAsRole annotation on a protected method (e.g. @RunAsRole("ROLE_AUDITOR")) and, if found, will add the given authority (ROLE_AUDITOR in this case) to the list of granted authorities. RunAsRole itself is just a simple custom annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RunAsRole {
    String value();
}

2) Instantiate the manager:

<bean id="runAsManager"
    class="org.springframework.security.access.intercept.RunAsManagerImpl">
  <property name="key" value="my_run_as_key"/>
</bean>

3) Register it:

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

4) Example usage in a Controller:

@Controller
public class TransactionLogController {

   @PreAuthorize("hasRole('ROLE_REGISTERED_USER')") //Authority needed to access the method
   @RunAsRole("ROLE_AUDITOR") //Authority added by RunAsManager
   @RequestMapping(value = "/transactions",  method = RequestMethod.GET) //Spring MVC configuration. Not related to security
   @ResponseBody //Spring MVC configuration. Not related to security
   public List<Transaction> getTransactionLog(...) {
    ... //Invoke something in the backend requiring ROLE_AUDITOR
   {

   ... //User does not have ROLE_AUDITOR here
}

EDIT: The value of key in RunAsManagerImpl can be anything you want. Here's the excerpt from Spring docs on its use:

To ensure malicious code does not create a RunAsUserToken and present it for guaranteed acceptance by the RunAsImplAuthenticationProvider, the hash of a key is stored in all generated tokens. The RunAsManagerImpl and RunAsImplAuthenticationProvider is created in the bean context with the same key:

<bean id="runAsManager"
    class="org.springframework.security.access.intercept.RunAsManagerImpl">

<bean id="runAsAuthenticationProvider"
    class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider">

By using the same key, each RunAsUserToken can be validated it was created by an approved RunAsManagerImpl. The RunAsUserToken is immutable after creation for security reasons.

Upvotes: 7

Laures
Laures

Reputation: 5489

I solved this by implementing my own RunAsManager that checks for a custom annotation on the invoked method and returns the appropriate Token.

works great.

Upvotes: 2

Related Questions