Krishna Chaitanya
Krishna Chaitanya

Reputation: 2663

What is the use of @RunAs javax security annotation?

I tried understanding the use of @RunAs annotation from the java docs but I did not understand the use of it. Can anyone please explain?

What I understand is, in some cases, if an authenticated user with a different role, wants to access an ejb method which is allowed to be accessed by users with certain roles only, then the caller ejb can annotate itself to be run as the expected role and can access the ejb method.

So I wrote the below code but my understanding is wrong.

JAX-RS Class:

package com.jee.beginner.rest;

import java.security.Principal;

import javax.inject.Inject;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.groups.ConvertGroup;
import javax.validation.groups.Default;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;

import com.jee.beginner.custom.validation.Create;
import com.jee.beginner.custom.validation.Update;
import com.jee.beginner.domain.Student;
import com.jee.beginner.service.StudentService;
import com.jee.beginner.service.proxy.StudentServiceProxy;

@Path("student")
public class StudentResource {

    @Inject
    private Principal principal;

    @Inject
    private StudentService studentService;

    @Inject
    private StudentServiceProxy studentServiceProxy;

    @GET
    @Path("details/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Student getDetails(@PathParam("id") @Min(value = 2, message = "ID cannot be less than 2") int id,
            @QueryParam("id") int qid, @Context UriInfo uriInfo) {

        return studentServiceProxy.getDetails(id);
    }

    @POST
    @Path("new")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Student addStudent(@Valid @ConvertGroup(from = Default.class, to = Create.class) final Student student) {

        return studentService.addStudent(student);
    }

    @POST
    @Path("update")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Student updateStudent(@Valid @ConvertGroup(from = Default.class, to = Update.class) final Student student) {

        return student;
    }
}

A proxy class. This class is annotated as @RunAs("admin")

package com.jee.beginner.service.proxy;

import javax.annotation.security.RunAs;
import javax.ejb.Stateless;
import javax.inject.Inject;

import com.jee.beginner.domain.Student;
import com.jee.beginner.service.StudentService;

@RunAs("admin")
@Stateless
public class StudentServiceProxy {

    @Inject
    private StudentService studentService;

    public Student getDetails(int id) {
        return studentService.getDetails(id);
    }
}

Service class:

package com.jee.beginner.service;

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJBContext;
import javax.ejb.Stateless;

import com.jee.beginner.domain.Student;

@Stateless
public class StudentService {

    @Resource
    private EJBContext context;

    @RolesAllowed({ "admin", "guest" })
    public Student addStudent(final Student student) {
        System.out.println(context.isCallerInRole("admin"));
        return student;
    }

    @RolesAllowed({ "admin" })
    public Student getDetails(int id) {
        Student student = new Student();
        student.setId(id);
        student.setName("noname");
        return student;
    }
}

I created a realm and added two users

UserA - admin, UserB - guest

Without the RunAs annotation, UserA was able to access the method as expected and UserB was not able to access the method as expected.

As soon as I added the RunAs annotation, both the users were not able to access getDetails method.

What I thought was UserB would be able to access the method now because the Proxy is annotated with RunAs admin and I thought StudentService would treat the user as admin role. But infact what happened was UserA also was not able to access the method.

Can anyone please explain me the significance of RunAs annotation?

Upvotes: 1

Views: 1919

Answers (2)

areus
areus

Reputation: 2948

The @RunAs annotation can be used for the use cases that @Steve C points out, but also for exactly the use case you are describing.

Your code and your assumptions are correct. It's not working because the way some containers (Wildfly for example) implement EJB method permissions by default. When no security annotations are used at all, all methods are assumed to be unchecked, as if they where annotated with @PermitAll. But when any security annotation is used on your deployment and a method hasn't an explicit permissions (at the class level or method level), Wildfly treats it as if they had a @DenyAll.

So, your StudentService is correct, but in your StudentServiceProxy, the getDetails method hasn't any method permission. You should annotate it (at the method level or class level) with @PermitAll (any user, event unauthenicated ones can execute it), @RolesAllowed({ "**" }) (any authenticated user can execute it), or @RolesAllowed({ "admin", "guest" }) (users with admin or guest role an execute it).

If using wildfly/jboss you can also change the default behaviour of the ejb3 subsystem. Check: https://docs.jboss.org/author/display/WFLY/Securing+EJBs

Upvotes: 3

Steve C
Steve C

Reputation: 19435

Normally, an EJB method will be synchronously invoked as the result of some action by an authenticated user. The application server propagates the security context of the user along with the invocation and the EJB container can use this context to verify that the caller is associated with the permitted roles for that method.

However, there are situations where an EJB method can be invoked without the participation of an authenticated user. These situations include:

  • execution of an EJB timer
  • execution of a message driven bean (MDB)

For example, you may have defined a periodic service that should be executed:

@Stateless
public class StudentService {

     @Timeout
     @Schedule(...)
     @RunAs("admin")
     public void periodicCheck(Timer timer) {
         ...
     }

}

Using the javax.annotation.security.RunAs here denotes that this method should be executed with the "admin" role.

Upvotes: 3

Related Questions