JAX-RS 2.0 Security Tutorial in JavaEE and JBoss

This tutorial will summarize how the author was able to call a secured rest webservice using resteasy. We will not go into detail on how we build the entire project since the code is already pushed at github. Basically we will just note down the most important part of the process:

Note that our project was based on the linked in the reference below, we just made some modifications so that it will work on a newer version of jboss.

Tech Stack

  1. JavaEE6 / 7
  2. JBoss EAP 6.2

Things to remember

  1. Download resteasy-jaxrs-3.0.6.Final-all.zip
    1. Extract the zipped file and inside it find the folder: resteasy-jboss-modules-3.0.6.Final
    2. Copy all the folders inside it and paste into JBOSS_HOME/modules.
  2. In web.xml, we don't add any resteasy related parameters, instead it should look like this.
  3. <?xml version="1.0" encoding="UTF-8"?>

    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>JAX-RS 2.0 Security Demo</display-name>

    </web-app>
  4. Create a web service activator.
  5. package com.kalidadbiz;

    import javax.ws.rs.ApplicationPath;
    import javax.ws.rs.core.Application;

    /**
    * @author Edward P. Legaspi
    **/
    @ApplicationPath("/api/rest")
    public class JaxRsActivator extends Application {

    }
  6. And finally the rest request interceptor:
  7. package com.kalidadbiz;

    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import java.util.StringTokenizer;

    import javax.annotation.security.DenyAll;
    import javax.annotation.security.PermitAll;
    import javax.annotation.security.RolesAllowed;
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.core.MultivaluedMap;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.ext.ExceptionMapper;
    import javax.ws.rs.ext.Provider;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.jboss.resteasy.core.Headers;
    import org.jboss.resteasy.core.ResourceMethodInvoker;
    import org.jboss.resteasy.core.ServerResponse;
    import org.jboss.resteasy.util.Base64;

    /**
    * @author Edward P. Legaspi
    *
    * http://java.dzone.com/articles/java-ee-7-and-jax-rs-20
    **/
    @Provider
    public class RESTSecurityInterceptor implements
    javax.ws.rs.container.ContainerRequestFilter,
    ExceptionMapper<Exception> {

    private Log log = LogFactory.getLog(RESTSecurityInterceptor.class);

    private static final String AUTHORIZATION_PROPERTY = "Authorization";
    private static final String AUTHENTICATION_SCHEME = "Basic";
    private static final ServerResponse ACCESS_DENIED = new ServerResponse(
    "Access denied for this resource", 401, new Headers<Object>());;
    private static final ServerResponse ACCESS_FORBIDDEN = new ServerResponse(
    "Nobody can access this resource", 403, new Headers<Object>());;
    private static final ServerResponse SERVER_ERROR = new ServerResponse(
    "INTERNAL SERVER ERROR", 500, new Headers<Object>());

    @Override
    public void filter(ContainerRequestContext requestContext) {
    log.info("filter");

    ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext
    .getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
    Method method = methodInvoker.getMethod();
    // Access allowed for all
    if (!method.isAnnotationPresent(PermitAll.class)) {
    // Access denied for all
    if (method.isAnnotationPresent(DenyAll.class)) {
    requestContext.abortWith(ACCESS_FORBIDDEN);
    return;
    }

    // Get request headers
    final MultivaluedMap<String, String> headers = requestContext
    .getHeaders();

    // Fetch authorization header
    final List<String> authorization = headers
    .get(AUTHORIZATION_PROPERTY);

    // If no authorization information present; block access
    if (authorization == null || authorization.isEmpty()) {
    requestContext.abortWith(ACCESS_DENIED);
    return;
    }

    // Get encoded username and password
    final String encodedUserPassword = authorization.get(0)
    .replaceFirst(AUTHENTICATION_SCHEME + " ", "");

    // Decode username and password
    String usernameAndPassword = null;
    try {
    usernameAndPassword = new String(
    Base64.decode(encodedUserPassword));
    } catch (IOException e) {
    requestContext.abortWith(SERVER_ERROR);
    return;
    }

    // Split username and password tokens
    final StringTokenizer tokenizer = new StringTokenizer(
    usernameAndPassword, ":");
    final String username = tokenizer.nextToken();
    final String password = tokenizer.nextToken();

    // Verifying Username and password
    log.info(username);
    log.info(password);

    // Verify user access
    if (method.isAnnotationPresent(RolesAllowed.class)) {
    RolesAllowed rolesAnnotation = method
    .getAnnotation(RolesAllowed.class);
    Set<String> rolesSet = new HashSet<String>(
    Arrays.asList(rolesAnnotation.value()));

    // Is user valid?
    if (!isUserAllowed(username, password, rolesSet)) {
    requestContext.abortWith(ACCESS_DENIED);
    return;
    }
    }
    }
    }

    private boolean isUserAllowed(final String username, final String password,
    final Set<String> rolesSet) {
    boolean isAllowed = false;

    // Step 1. Fetch password from database and match with password in
    // argument
    // If both match then get the defined role for user from database and
    // continue; else return isAllowed [false]
    // Access the database and do this part yourself
    // String userRole = userMgr.getUserRole(username);
    String userRole = "ADMIN";

    // Step 2. Verify user role
    if (rolesSet.contains(userRole)) {
    isAllowed = true;
    }
    return isAllowed;
    }

    @Override
    public Response toResponse(Exception exception) {
    // TODO Auto-generated method stub
    return null;
    }

    }
  8. To test if the filter will really be triggered when there is a rest web service request, I've provided an action bean and a jersey client that sends a request with username and password
package com.kalidadbiz;

import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

/**
* @author Edward P. Legaspi
**/
public class RestClient {
private Log log = LogFactory.getLog(RestClient.class);

private String host;
private String api;
private Properties properties = new Properties();

public RestClient() {

}

public RestClient(String host, String api) {
this.host = host;
this.api = api;
}

public void addParam(String key, String value) {
properties.put(key, value);
}

public String execute() {
try {
Client client = Client.create();
client.addFilter(new HTTPBasicAuthFilter("edward", "edward"));

String params = "";
if (properties != null) {
for (String key : properties.stringPropertyNames()) {
String value = properties.getProperty(key);
if (params != null) {
params += "&";
}
params += key + "=" + value;
}
}

String apiUrl = host + "/" + api;
if (params != null && params.length() > 0) {
apiUrl = apiUrl + "?" + params;
}
WebResource webResource = client.resource(apiUrl);

ClientResponse response = webResource.accept("application/json")
.get(ClientResponse.class);

if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}

return response.getEntity(String.class);
} catch (Exception e) {
log.error(e.getMessage());
return "";
}
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public String getApi() {
return api;
}

public void setApi(String api) {
this.api = api;
}
}

And lastly, don't forget to encrypt your password :-).

Github Repository

References:

http://howtodoinjava.com/2013/07/25/jax-rs-2-0-resteasy-3-0-2-final-security-tutorial/

0 nhận xét:

Đăng nhận xét