Hi All,
Thanks for reading this post...
Let's talk about how to implement a set of classes using REST concepts, and basic control access to the WebServices. An interceptor will be responsible for reading the request and manage the access control to the resources.
First of all, it will be necessary to define the pom.xml (maven resources) with the following dependencies:
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-crypto</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
JBoss EAP 6.4 and Resteasy API 2.3.10.Final were used to validate the sample code.
- Rest Activator
@ApplicationPath("/rest")
public class WsRestActivator extends Application {
}
- Exception Class
public class ServiceAPIException extends Exception {
public ServiceAPIException(String message) {
super(message);
}
public ServiceAPIException(Exception e) {
super(e);
}
public ServiceAPIException(String msg, Throwable t) {
super(msg, t);
}
}
@Provider
public class ExceptionHttpStatusResolver implements ExceptionMapper<ServiceAPIException> {
@Override
public Response toResponse(ServiceAPIException exception) {
Response.Status httpStatus = Response.Status.INTERNAL_SERVER_ERROR;
if (exception instanceof ServiceAPIException)
httpStatus = Response.Status.BAD_REQUEST;
return Response.status(httpStatus).entity(exception.getMessage()).build();
}
}
- Annotation to allow access to the Webservice methods
@Documented
@Retention (RUNTIME)
@Target({TYPE, METHOD})
public @interface WsAccessMethodPermition {
}
- Service Abstracttion
public abstract class ServiceRest {
protected void handleException(String msg, Throwable t) throws ServiceAPIException {
throw new ServiceAPIException(msg, t);
}
}
- Security Abstraction for Resource Classes
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.security.smime.EnvelopedOutput;
public abstract class SecureResource extends ServiceRest {
public static final String USER_CERTIFICATE = "user-certificate";
private static CertificateFactory factory;
static {
try {
factory = CertificateFactory.getInstance("X.509");
} catch (Exception e) {
}
}
protected X509Certificate extractCertificate(HttpServletRequest request) {
try {
InputStream is = new ByteArrayInputStream((byte[]) request.getAttribute(
SecureResource.USER_CERTIFICATE));
return (X509Certificate) factory.generateCertificate(is);
} catch (CertificateException e) {
return null;
}
}
protected EnvelopedOutput encryptResponse(HttpServletRequest request, Object result) {
X509Certificate certificate = extractCertificate(request);
return encryptResponse(certificate, result);
}
protected EnvelopedOutput encryptResponse(X509Certificate certificate, Object result) {
// Object user = UserContext.getUser(result);
EnvelopedOutput output = new EnvelopedOutput(
new Object()/* your user object */, MediaType.APPLICATION_JSON);
output.setCertificate(certificate);
return output;
}
}
- Interceptor
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;
import org.jboss.resteasy.util.Base64;
@Provider
@ServerInterceptor
public class SecurityInterceptor implements PreProcessInterceptor, AcceptedByMethod {
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final String AUTHENTICATION_SCHEME = "Basic";
private static final ServerResponse ACCESS_DENIED = new ServerResponse("Access Denied!", 401, new Headers<Object>());
private static final ServerResponse ACCESS_FORBIDDEN = new ServerResponse("Access Forbidden!", 403, new Headers<Object>());
private static final ServerResponse SERVER_ERROR = new ServerResponse("Internal Error!", 500, new Headers<Object>());
@Override
public ServerResponse preProcess(HttpRequest request, ResourceMethod methodInvoked) throws Failure, WebApplicationException {
Method method = methodInvoked.getMethod();
if (method.isAnnotationPresent(WsAccessMethodPermition.class) ||
method.isAnnotationPresent(PermitAll.class)) {
return null;
}
// Access denied for all
if (method.isAnnotationPresent(DenyAll.class)) {
return ACCESS_FORBIDDEN;
}
// Get request headers
final HttpHeaders headers = request.getHttpHeaders();
// Fetch authorization header
final List<String> authorization = headers.getRequestHeader(AUTHORIZATION_PROPERTY);
// If no authorization information present; block access
if (authorization == null || authorization.isEmpty()) {
return ACCESS_DENIED;
}
// Get encoded username and password
final String encodedUserPassword = authorization.get(0).replaceFirst(
AUTHENTICATION_SCHEME.concat(" "), "");
// Decode username and password
String usernameAndPassword;
try {
usernameAndPassword = new String(Base64.decode(encodedUserPassword));
} catch (IOException e) {
return SERVER_ERROR;
}
// Split username and password tokens
final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
final String username = tokenizer.nextToken();
final String password = tokenizer.nextToken();
// Verify user access
if (!isUserAllowed(request, username, password)) {
return ACCESS_DENIED;
}
return null;
}
@SuppressWarnings("serial")
private boolean isUserAllowed(final HttpRequest request, final String username, final String password) {
UserWS user = new UserWS(username, password);
if (user != null) {
if (!user.getRoles().isEmpty() && !user.hasPermission()) {
byte[] certificate = user.getCertificate();
request.setAttribute(SecureResource.USER_CERTIFICATE, certificate);
return true;
}
}
return false;
}
@Override
public boolean accept(@SuppressWarnings("rawtypes") Class declaring, Method method) {
// bypass authentication - get methods for instance
if (method.isAnnotationPresent(WsAccessMethodPermition.class) ||
method.isAnnotationPresent(PermitAll.class)) {
return false;
}
// Access denied for all
if (method.isAnnotationPresent(DenyAll.class)) {
// TODO here for instance log denied access
return true;
}
return true;
}
}
And now...the resource...
- WebService Implementation
@Path("/test")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class TestResourceWS extends SecureResource {
@POST
@Path("/info/")
@Produces(MediaType.APPLICATION_JSON)
@WsAccessMethodPermition
@Override
public String info() throws ServiceAPIException {
return "Hello, Access OK!";
}
@POST
@Path("/infodenied/")
@Produces(MediaType.APPLICATION_JSON)
@Override
public void infodenied() throws ServiceAPIException {
// "denied!";
}
}
- Sample invocation
http://<host>/<app-name>/rest/test/info
OK!
http://<host>/<app-name>/rest/test/infodenied
Access Denied!
- WS Client Sample...post methods for instance
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Authorization", "Basic " + base64Creds);
headers.add("Content-Type", "application/json");
RestTemplate restTemplate = new RestTemplate();
HttpEntity<ObjectToPass> request = new HttpEntity<ObjectToPass>(ObjectToPass, headers);
restTemplate.postForObject("http://<host>/<app>/rest/test/info", request, Boolean.class);
Thanks a lot and regards!