blogger templates blogger widgets
This is part of a list of blog posts.
To browse the contents go to

JAX-RS: Security

Let's try to secure a JAX-RS service namely /NotesApp/notes that we did earlier.

The JAX-RS runtime environment from IBM is driven by a servlet derived from the Apache Wink project. Within the WebSphere Application Server environment, the lifecycle of servlets is managed in the web container. Therefore, the security services offered by the web container are applicable to REST resources that are deployed in WebSphere Application Server.

As an example I used FORM based authentication.

Let's define the rest application class and resources.

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

@ApplicationPath("/NotesApp")
public class NotesApplication extends Application{

}

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name="note")
@XmlType
public class Note {

    private Long id;
    private String text;

    public Note() {
    }

    public Note(Long id, String text) {
        this.id = id;
        this.text = text;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

@XmlRootElement(name="notes")
public class NotesList {

    @XmlElement(name="note")
    private List<note> notes = new ArrayList<note>();

    public NotesList() {
    }

    @XmlTransient
    public List<note> getNotes() {
        return notes;
    }

    public void setNotes(List<note> notes) {
        this.notes = notes;
    }
}

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/notes")
public class NotesResource {
 @GET
 @Produces(MediaType.APPLICATION_XML)
 public NotesList list() {
  List<note> listOfNotes = new ArrayList<note>();
  listOfNotes.add(new Note(1L, "one"));
  listOfNotes.add(new Note(2L, "two"));
  NotesList response = new NotesList();
  response.getNotes().addAll(listOfNotes);
  return response;
 }
}


Login page,


<%@page language="java"
 contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<html>
<head>
<title>login</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
</head>
<body>
 <h2>
Form Login</h2>
<form method="post" action="j_security_check">
<p>
Enter user ID and password 
User ID <input type="text" size="20" name="j_username"> 
                        Password <input type="password" size="20" name="j_password"> 
<strong> And then click this button:</strong>
   <input type="submit" name="login" value="Login">
  </p>
</form>
</body>
</html>


Add the security constraint information to web.xml

<security-constraint>
 <display-name>SecurityConstraintForResources</display-name>
 <web-resource-collection>
  <web-resource-name>SecureREST</web-resource-name>
  <description>NotesApp/notes rest end-point is a protected resource</description>
  <url-pattern>/NotesApp/notes</url-pattern>
  <http-method>GET</http-method>
 </web-resource-collection>
 <auth-constraint>
  <role-name>ServletAdmins</role-name>
 </auth-constraint>
 <user-data-constraint>
  <transport-guarantee>NONE</transport-guarantee>
 </user-data-constraint>
</security-constraint>

<login-config>
 <auth-method>FORM</auth-method>
 <realm-name>defaultWIMFileBasedRealm</realm-name>
 <form-login-config><form-login-page>/login.jsp</form-login-page><form-error-page>/error.jsp</form-error-page></form-login-config></login-config>
<security-role>
 <role-name>ServletAdmins</role-name>
</security-role>

In the application.xml of EAR file. Add the security role used by your application.

<?xml version="1.0" encoding="UTF-8"?>
<application>
 <module ...>
  <web> ... </web>
 </module>
<security-role>
 <role-name>ServletAdmins</role-name>
</security-role>
</application>


You need to then map the security role defined in the web app to the actual roles of the authentication server.
I have mapped it to a group. So in ibm-application-bnd.xml (located at same place as application.xml)

<?xml version="1.0" encoding="UTF-8"?>
<application-bnd
 xmlns="http://websphere.ibm.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-application-bnd_1_1.xsd"
 version="1.1">

<security-role name="ServletAdmins">
 <group name="SecretUserGroup" />
</security-role>
</application-bnd>

Note that we used defaultWIMFileBasedRealm as the realm.
Making appropriate configuration in WAS console.

1. Check the selected realm. defaultWIMFileBasedRealm is the default realm that's based on a file based repository.

2. Create users and and add users to a group


JAX-RS Client

We use the apache HttpClient to programmatically hit the url and login. Once the login is successful we obtain the cookies and make request to the rest resource.

A sample usage of HttpClient.
HttpClient client = new DefaultHttpClient(); HttpGet request = new
HttpGet("http://www.google.com"); HttpResponse response =
client.execute(request);

// Get the response 
BufferedReader rd = new BufferedReader(new
InputStreamReader(response .getEntity().getContent()));

String line = ""; while ((line = rd.readLine()) != null) {
System.out.println(line); }


RS client,


List<uri> redirectLocations = null;
String loginURL = null;
BasicCookieStore cookieStore = new BasicCookieStore();
// instead of the default using a custom httpClient available from the
// HttpClientBuilder factory
CloseableHttpClient httpclient = HttpClients.custom()
  .setDefaultCookieStore(cookieStore).build();

// Try to fetch the resource; but redirects; so fetches redirect url =
// login url
try {
 HttpClientContext context = HttpClientContext.create();
 HttpGet httpget = new HttpGet(
   "http://localhost:9080/SRServer/NotesApp/notes");
 CloseableHttpResponse response1 = httpclient.execute(httpget,
   context);
 try {
  HttpEntity entity = response1.getEntity();
  // Ensures that the entity content is fully consumed and the
  // content stream,
  // if exists, is closed.
  EntityUtils.consume(entity);

  System.out
    .println("Resource get: " + response1.getStatusLine());

  redirectLocations = context.getRedirectLocations();
  loginURL = redirectLocations.get(0).toASCIIString();

  System.out.println("Initial set of cookies:");
  List<cookie> cookies = cookieStore.getCookies();
  if (cookies.isEmpty()) {
   System.out.println("None");
  } else {
   for (int i = 0; i < cookies.size(); i++) {
    System.out.println("- " + cookies.get(i).toString());
   }
  }
 } finally {
  response1.close();
 }

 // Trying to login
 HttpUriRequest login = RequestBuilder.post()
   .setUri(new URI(loginURL + "/j_security_check"))
   .addParameter("j_username", "userone")
   .addParameter("j_password", "userone").build();
 CloseableHttpResponse response2 = httpclient.execute(login);
 try {
  HttpEntity entity = response2.getEntity();
  // Ensures that the entity content is fully consumed and the
  // content stream,
  // if exists, is closed.
  EntityUtils.consume(entity);
  System.out.println("Login form get: "
    + response2.getStatusLine());

  System.out.println("Post logon cookies:");
  List<Cookie> cookies = cookieStore.getCookies();
  if (cookies.isEmpty()) {
   System.out.println("None");
  } else {
   for (int i = 0; i < cookies.size(); i++) {
    System.out.println("- " + cookies.get(i).toString());
   }
  }
 } finally {
  response2.close();
 }

 // Trying to fetch the resource again, after a successful login
 httpget = new HttpGet(
   "http://localhost:9080/SRServer/NotesApp/notes");
 CloseableHttpResponse response3 = httpclient.execute(httpget);
 try {
  HttpEntity entity = response3.getEntity();

  System.out
    .println("Resource get: " + response3.getStatusLine());

  BufferedReader rd = new BufferedReader(new InputStreamReader(
    response3.getEntity().getContent()));
  String line = "";
  while ((line = rd.readLine()) != null) {
   System.out.println(line);
  }
  EntityUtils.consume(entity); // this should be done here only
          // because the stream gets
          // closed
  System.out.println("Initial set of cookies:");
  List<Cookie> cookies = cookieStore.getCookies();
  if (cookies.isEmpty()) {
   System.out.println("None");
  } else {
   for (int i = 0; i < cookies.size(); i++) {
    System.out.println("- " + cookies.get(i).toString());
   }
  }
 } finally {
  response3.close();
 }

} catch (URISyntaxException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
} finally {
 httpclient.close();
}

On websphere there is a chance that you would get
java.lang.NoSuchFieldError: org/apache/http/message/BasicLineFormatter.INSTANCE
This is because Websphere server runtime API uses a older version of apache libraries.

For compile time change the order within the RAD/eclipse.

For runtime change the settings in WAS.


Note that the settings wouldn't be editable if run/deployed from RAD. Do a manual deployment.

No comments:

Post a Comment