Search code examples
javaspringspring-securityamqpspring-amqp

how to establish security context for transactions done from spring amqp messages


I have a spring boot application for which I secure methods using spring security annotations as shown below.

public interface UserService {

    @PreAuthorize("hasRole('ADMIN')")
    List<User> findAllUsers();

    @PostAuthorize ("returnObject.type == authentication.name")
    User findById(int id);

    @PreAuthorize("hasRole('ADMIN')")
    void updateUser(User user);

    @PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
    void deleteUser(int id);

}

The problem that I have here is that there are implementations which are called via @RabbitListener methods which does not have any security context because it's not a web request.

I can expect the clients to pass the usernames via message headers sp I could use message.getMessageProperties().getHeaders().get("username") to get the username for that request.

But still I believe there won't be a straightforward approach as the method level annotations won't be evaluated because they are not under security context.

Is there any approach via which I can establish a security context for a spring amqp messages?


Solution

  • Just add the context...

    @SpringBootApplication
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class So49957413Application extends GlobalAuthenticationConfigurerAdapter {
    
        public static void main(String[] args) {
            SpringApplication.run(So49957413Application.class, args);
        }
    
        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .withUser("foo").password("bar").roles("baz");
        }
    
        @Autowired
        private Foo foo;
    
    
        @RabbitListener(queues = "foo")
        public void listen(Message in) {
            try {
                SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                ctx.setAuthentication(
                    new UsernamePasswordAuthenticationToken(in.getMessageProperties().getHeaders().get("user"), "bar"));
                SecurityContextHolder.setContext(ctx);
                this.foo.method1();
                try {
                    this.foo.method2();
                }
                catch (AccessDeniedException e) {
                    System.out.println("Denied access to method2");
                }
            }
            finally {
                SecurityContextHolder.clearContext();
            }
        }
    
        @Bean
        public Foo foo() {
            return new Foo();
        }
    
        public static class Foo {
    
            @PreAuthorize("hasRole('baz')")
            public void method1() {
                System.out.println("in method1");
            }
    
            @PreAuthorize("hasRole('qux')")
            public void method2() {
                System.out.println("in method2");
            }
    
        }
    }
    

    and

    in method1
    Denied access to method2
    

    It would be better to add/remove the security context in an advice, in the listener container's advice chain, to avoid mixing the security code with your listener logic.