Background
I wanted to write a test case for web security using FORM authentication in the AS7 testsuite. I chose the testsuite/integration submodule.
Requirements
- I wanted to be able to run the test case in Eclipse.
- I wanted to use JPDA to set breakpoints in the AS7 codebase.
Choices of Technologies
- Arquilian
- Shrinkwrap
- JUnit
- Eclipse
Note: Both Arquilian and Shrinkwrap are available in the AS7 development environment by default.
Test Case
/* * JBoss, Home of Professional Open Source. * Copyright (c) 2011, Red Hat, Inc., and individual contributors <snip> Copyright header */ package org.jboss.as.testsuite.integration.websecurity; import static org.junit.Assert.assertEquals; import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.cookie.Cookie; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.jboss.arquillian.api.Deployment; import org.jboss.arquillian.api.Run; import org.jboss.arquillian.api.RunModeType; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Test; import org.junit.runner.RunWith; /** * Unit Test web security * * @author Anil Saldhana */ @RunWith(Arquillian.class) @Run(RunModeType.AS_CLIENT) public class WebSecurityFORMTestCase { private static final String URL = "http://localhost:8080/web-secure/secured/"; @Deployment public static WebArchive deployment() { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); URL webxml = tccl.getResource("web-secure.war/web.xml"); WebArchive war = ShrinkWrap.create(WebArchive.class, "web-secure.war"); war.addClass(SecuredServlet.class); URL userProp = tccl.getResource("web-secure.war/users.properties"); URL roleProp = tccl.getResource("web-secure.war/roles.properties"); URL loginJSP = tccl.getResource("web-secure.war/login.jsp"); URL errorJSP = tccl.getResource("web-secure.war/error.jsp"); war.addResource(loginJSP, "login.jsp"); war.addResource(errorJSP, "error.jsp"); war.addResource(userProp, "/WEB-INF/classes/users.properties"); war.addResource(roleProp, "/WEB-INF/classes/roles.properties"); war.setWebXML(webxml); return war; } /** * Test with user "anil" who has the right password and the right role to access the servlet * * @throws Exception */ @Test public void testSuccessfulAuth() throws Exception { makeCall("anil", "anil", 200); } /** * <p> * Test with user "marcus" who has the right password but does not have the right role * </p> * <p> * Should be a HTTP/403 * </p> * * @throws Exception */ @Test public void testUnsuccessfulAuth() throws Exception { makeCall("marcus", "marcus", 403); } protected void makeCall(String user, String pass, int expectedStatusCode) throws Exception { DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpGet httpget = new HttpGet(URL); HttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); if (entity != null) entity.consumeContent(); // We should get the Login Page StatusLine statusLine = response.getStatusLine(); System.out.println("Login form get: " + statusLine); assertEquals(200, statusLine.getStatusCode()); System.out.println("Initial set of cookies:"); List<Cookie> cookies = httpclient.getCookieStore().getCookies(); if (cookies.isEmpty()) { System.out.println("None"); } else { for (int i = 0; i < cookies.size(); i++) { System.out.println("- " + cookies.get(i).toString()); } } // We should now login with the user name and password HttpPost httpost = new HttpPost(URL + "/j_security_check"); List<NameValuePair> nvps = new ArrayList<NameValuePair>(); nvps.add(new BasicNameValuePair("j_username", user)); nvps.add(new BasicNameValuePair("j_password", pass)); httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); response = httpclient.execute(httpost); entity = response.getEntity(); if (entity != null) entity.consumeContent(); statusLine = response.getStatusLine(); // Post authentication - we have a 302 assertEquals(302, statusLine.getStatusCode()); Header locationHeader = response.getFirstHeader("Location"); String location = locationHeader.getValue(); HttpGet httpGet = new HttpGet(location); response = httpclient.execute(httpGet); entity = response.getEntity(); if (entity != null) entity.consumeContent(); System.out.println("Post logon cookies:"); cookies = httpclient.getCookieStore().getCookies(); if (cookies.isEmpty()) { System.out.println("None"); } else { for (int i = 0; i < cookies.size(); i++) { System.out.println("- " + cookies.get(i).toString()); } } // Either the authentication passed or failed based on the expected status code statusLine = response.getStatusLine(); assertEquals(expectedStatusCode, statusLine.getStatusCode()); } finally { // When HttpClient instance is no longer needed, // shut down the connection manager to ensure // immediate deallocation of all system resources httpclient.getConnectionManager().shutdown(); } } }
In this test case, I am specifying that the test case should run using Arquilian (via @RunWith) annotation. Plus I am telling Arquilian to run the test as a client test case and not inside AS7. [via @Run(RunModeType.AS_CLIENT) ] If you want the test to run inside AS7, then you have to use IN_CONTAINER (which is default anyway).
Also note above. that I am adding users.properties and roles.properties to WEB-INF/classes directory inside the Web Archive.
Web Deployment
I need the following Items in my web archive
- Servlet
- web.xml
- Two properties files (users.properties and roles.properties)
Let us look at the servlet
/* * JBoss, Home of Professional Open Source. * Copyright (c) 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * <SNIP> */ package org.jboss.as.testsuite.integration.websecurity; import java.io.IOException; import java.io.Writer; import javax.servlet.ServletException; import javax.servlet.annotation.HttpConstraint; import javax.servlet.annotation.ServletSecurity; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * A simple servlet that just writes back a string * * @author Anil Saldhana */ @WebServlet(urlPatterns = { "/secured/" }) @ServletSecurity(@HttpConstraint(rolesAllowed = { "gooduser" })) public class SecuredServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Writer writer = resp.getWriter(); writer.write("GOOD"); } }
My test resources were in a folder as
web-secure.war/
login.jsp
error.jsp
users.properties
roles.properties
web.xml
Where should I look for the source code?
https://github.com/anilsaldhana/jboss-as/commit/0efdc673a45d6d172559183243a99e0927e33e65
Show me the test in action, buddy
anil@localhost:~/as7/jboss-as/testsuite/integration$ mvn clean install Running org.jboss.as.testsuite.integration.websecurity.WebSecurityFORMTestCase Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.531 sec
Troubleshooting
- How do I run AS7 with JPDA enabled?
- Uncomment the following line in bin/standalone.conf
#JAVA_OPTS="$JAVA_OPTS -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y"
- Any Eclipse settings for the test case?
- VM arguments in the eclipse config for the test case was
-Djboss.home=/home/anil/as7/jboss-as/testsuite/integration/../../build/target/jboss-7.0.0.Beta2
- How do I see what is in the web archive ShrinkWrap creates? You definitely need to add this always.
System.out.println(war.toString(true));
- What about when Arquilian is running in a managed mode?
- Stuart Douglas gave some insight into this.
There are two ways, you can either start a standalone instance and attach your debugger to it normally, and then run mvn install -Premote -Dtest=com.my.Test Alternatively mvn install -Djboss.options="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787" And then attach. This way will not let you use -Dtest= though, so generally when I am debugging I use the remote profile.
Comments