We were testing a REST webservice developed in jersey through postman rest client. It is a POST method and is annotated with @RolesAllowed
. The full annotation the method is as follows:
@POST
@Path("/configuration")
@RolesAllowed("admin")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
When I requested this http://baseurl/configuration
with the expected HTTP body content, I got 403
response(it is expected since it is allowed only for admin as it seems).
My doubt is how to access this service with the specified role via rest client.
So it seems like you set up the RolesAllowedDynamicFeature
, but you have no authentication happening to set up the user and roles. What the RolesAllowedDynamicFeature
does is lookup the SecurityContext
, and calls the SecurityContext.isUserInRole(<"admin">)
to see if the user in the SecurityContext
has the role.
I imagine you don't know how the SecurityContext
is set. There are a couple of ways. The first is through the servlet authentication mechanism. You can see more at Securing Web Applications from the Java EE tutorial.
Basically you need to set up a security realm or security domain on the server. Every server has it's own specific way of setting it up. You can see an example here or how it would be done with Tomcat.
Basically the realm/domain contains the users allowed to access the web app. Those users have associated roles. When the servlet container does the authentication, whether it be Basic authentication or Form authentication, it looks up the user from the credentials, and if the user is authenticated, the user and its roles are associated with the request. Jersey gathers this information and puts it into the SecurityContext
for the request.
If this seems a bit complicated, an easier way to just forget the servlet container authentication and just create a Jersey filter, where you set the SecurityContext
yourself. You can see an example here. You can use whatever authentication scheme you want. The important part is setting the SecurityContext
with the user information, wherever you get it from, maybe a service that accesses a data store.
See Also:
Here is a complete example of the second option using the filter. The test is run by Jersey Test Framework. You can run the test as is
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.DatatypeConverter;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.internal.util.Base64;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.glassfish.jersey.test.JerseyTest;
import static junit.framework.Assert.*;
import org.junit.Test;
public class BasicAuthenticationTest extends JerseyTest {
@Provider
@Priority(Priorities.AUTHENTICATION)
public static class BasicAuthFilter implements ContainerRequestFilter {
private static final Logger LOGGER = Logger.getLogger(BasicAuthFilter.class.getName());
@Inject
private UserStore userStore;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authentication = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authentication == null) {
throw new AuthenticationException("Authentication credentials are required");
}
if (!authentication.startsWith("Basic ")) {
return;
}
authentication = authentication.substring("Basic ".length());
String[] values = new String(DatatypeConverter.parseBase64Binary(authentication),
Charset.forName("ASCII")).split(":");
if (values.length < 2) {
throw new WebApplicationException(400);
}
String username = values[0];
String password = values[1];
LOGGER.log(Level.INFO, "{0} - {1}", new Object[]{username, password});
User user = userStore.getUser(username);
if (user == null) {
throw new AuthenticationException("Authentication credentials are required");
}
if (!user.password.equals(password)) {
throw new AuthenticationException("Authentication credentials are required");
}
requestContext.setSecurityContext(new MySecurityContext(user));
}
}
static class MySecurityContext implements SecurityContext {
private final User user;
public MySecurityContext(User user) {
this.user = user;
}
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return user.username;
}
};
}
@Override
public boolean isUserInRole(String role) {
return role.equals(user.role);
}
@Override
public boolean isSecure() { return true; }
@Override
public String getAuthenticationScheme() {
return "Basic";
}
}
static class AuthenticationException extends WebApplicationException {
public AuthenticationException(String message) {
super(Response
.status(Status.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"" + "Dummy Realm" + "\"")
.type("text/plain")
.entity(message)
.build());
}
}
class User {
public final String username;
public final String role;
public final String password;
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
}
class UserStore {
public final Map<String, User> users = new ConcurrentHashMap<>();
public UserStore() {
users.put("peeskillet", new User("peeskillet", "secret", "USER"));
users.put("stackoverflow", new User("stackoverflow", "superSecret", "ADMIN"));
}
public User getUser(String username) {
return users.get(username);
}
}
private static final String USER_RESPONSE = "Secured User Stuff";
private static final String ADMIN_RESPONSE = "Secured Admin Stuff";
private static final String USER_ADMIN_STUFF = "Secured User Admin Stuff";
@Path("secured")
public static class SecuredResource {
@GET
@Path("userSecured")
@RolesAllowed("USER")
public String getUser() {
return USER_RESPONSE;
}
@GET
@Path("adminSecured")
@RolesAllowed("ADMIN")
public String getAdmin() {
return ADMIN_RESPONSE;
}
@GET
@Path("userAdminSecured")
@RolesAllowed({"USER", "ADMIN"})
public String getUserAdmin() {
return USER_ADMIN_STUFF;
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(SecuredResource.class)
.register(BasicAuthFilter.class)
.register(RolesAllowedDynamicFeature.class)
.register(new AbstractBinder(){
@Override
protected void configure() {
bind(new UserStore()).to(UserStore.class);
}
});
}
static String getBasicAuthHeader(String username, String password) {
return "Basic " + Base64.encodeAsString(username + ":" + password);
}
@Test
public void should_return_403_with_unauthorized_user() {
Response response = target("secured/userSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("stackoverflow", "superSecret"))
.get();
assertEquals(403, response.getStatus());
}
@Test
public void should_return_200_response_with_authorized_user() {
Response response = target("secured/userSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("peeskillet", "secret"))
.get();
assertEquals(200, response.getStatus());
assertEquals(USER_RESPONSE, response.readEntity(String.class));
}
@Test
public void should_return_403_with_unauthorized_admin() {
Response response = target("secured/adminSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("peeskillet", "secret"))
.get();
assertEquals(403, response.getStatus());
}
@Test
public void should_return_200_response_with_authorized_admin() {
Response response = target("secured/adminSecured")
.request()
.header(HttpHeaders.AUTHORIZATION,
getBasicAuthHeader("stackoverflow", "superSecret"))
.get();
assertEquals(200, response.getStatus());
assertEquals(ADMIN_RESPONSE, response.readEntity(String.class));
}
}
Here is the only dependency needed to run the test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With