Maven: Unable to Execute Spock Specs

PROBLEM

When running mvn clean test, Maven Surefire Plugin doesn’t pick up *Spec.groovy test files.

SOLUTION

By default, Maven Surefire Plugin is configured to execute test files with the following patterns: **/Test*.java, **/*Test.java and **/*TestCase.java.

To fix this, we need to modify the inclusion list for this plugin. Since both Java and Groovy files get compiled down to *.class, it is probably easier to just include *.class instead of *.java or *.groovy.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.17</version>
    <configuration>
        <includes>
            <include>**/Test*.class</include>
            <include>**/*Test.class</include>
            <include>**/*TestCase.class</include>
            <include>**/*Spec.class</include>
        </includes>
    </configuration>
</plugin>

Spring Security: Forcing URLs to use HTTPS

PROBLEM

Your web application supports both HTTP and HTTPS. You want to force all URLs to use HTTPS.

SOLUTION

Spring Security has a simple configuration that allows us to redirect all HTTP-based URLs to HTTPS. All we have to do is to set requires-channel="https" on <security:intercept-url/> tag.

For example:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
</security:http>

With this configuration, when the user hits http://server/app, it will be redirected to https://server/app.

If we are seeing this “redirect loop” error…

… and the server log went bananas…

"GET /app HTTP/1.1" 302 0 
"GET /app/ HTTP/1.1" 302 0 
"GET /app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 

… then, chances are we are not using the default HTTP port (80) and HTTPS port (443). To fix this, we have to specify the custom port mappings in the Spring Security configuration:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
	
	<security:port-mappings>
		<!-- Default ports -->
		<security:port-mapping http="80" https="443"/>
		<!-- Websphere default ports -->
		<security:port-mapping http="9080" https="9443"/>
		<!-- Tomcat default ports -->
		<security:port-mapping http="8080" https="8443"/>
		<!-- Jetty custom ports -->
		<security:port-mapping http="7777" https="7443"/>
	</security:port-mappings>
</security:http>

Now, when the user hits http://localhost:7777/app, it will be redirected to https://localhost:7443/app.

Java + HTTPS: Unable to Find Valid Certification Path to Requested Target

PROBLEM

When trying to invoke HTTPS-based web service (ex: https://server:1234/myapp/web-service), the following exceptions occurred:-

Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find 
valid certification path to requested target

Caused by: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

SOLUTION

I do not want to take any credits from @mkyong, but this author wrote a great solution to this problem at http://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/ . Kudos to him.

The only thing I want to point out here is the link to InstallCert.java in his blog is dead, but you can download it from https://code.google.com/p/java-use-examples/source/browse/trunk/src/com/aw/ad/util/InstallCert.java .

The long story short here is to run java InstallCert server:1234 to generate a file called jssecacerts. Then, drop this file in ${JAVA_HOME}/lib/security directory.

Maven + Jetty: Enabling SSL on Jetty Maven Plugin

PROBLEM

In order to run our web application using HTTPS, we need to enable SSL first on Jetty.

SOLUTION #1: Generating Certificate on the Fly

One clean solution by @PascalThivent is to recreate the certificate whenever Jetty starts. Not to steal any credits from him, but here’s a slightly modified configuration using more recent plugin versions:-

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>keytool-maven-plugin</artifactId>
    <version>1.5</version>
    <executions>
        <execution>
            <phase>generate-resources</phase>
            <id>clean</id>
            <goals>
                <goal>clean</goal>
            </goals>
        </execution>
        <execution>
            <phase>generate-resources</phase>
            <id>generateKeyPair</id>
            <goals>
                <goal>generateKeyPair</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <keystore>${project.build.directory}/jetty-ssl.keystore</keystore>
        <dname>cn=localhost</dname>
        <!--Both `keypass` and `storepass` must be at least 6 characters long. -->
        <keypass>jetty8</keypass>
        <storepass>jetty8</storepass>
        <alias>jetty8</alias>
        <keyalg>RSA</keyalg>
    </configuration>
</plugin>

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.14.v20131031</version>
    ...
    <configuration>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
                <port>7777</port>
            </connector>
            <connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
                <port>7443</port>
                <keystore>${project.build.directory}/jetty-ssl.keystore</keystore>
                <keyPassword>jetty8</keyPassword>
                <password>jetty8</password>
            </connector>
        </connectors>
    </configuration>
</plugin>

In this case, the generated certificate is stored under the project’s build directory (“target” directory) every time we run mvn clean jetty:run. This is a great solution because it allows me to easily pass my project to my peers without the need to worry about the generated certificate.

However, a downside to this approach is because the certificate is regenerated again and again, my browser keeps prompting me to add an exception whenever I refresh the web link.

… and after adding the exception for like 5 times, it begins to get very annoying.

SOLUTION #2: Generating Certificate Once and Reuse it

Instead of regenerating the certificate again and again, I decided to generate the certificate that will last for 99999 days and store it under src/main/config directory.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>keytool-maven-plugin</artifactId>
    <version>1.5</version>
    <configuration>
        <keystore>${project.basedir}/src/main/config/jetty-ssl.keystore</keystore>
        <dname>cn=localhost</dname>
        <!--Both `keypass` and `storepass` must be at least 6 characters long. -->
        <keypass>jetty8</keypass>
        <storepass>jetty8</storepass>
        <alias>jetty8</alias>
        <keyalg>RSA</keyalg>
        <validity>99999</validity>
    </configuration>
</plugin>

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.14.v20131031</version>
    ...
    <configuration>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.bio.SocketConnector">
                <port>7777</port>
            </connector>
            <connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
                <port>7443</port>
                <keystore>${project.basedir}/src/main/config/jetty-ssl.keystore</keystore>
                <keyPassword>jetty8</keyPassword>
                <password>jetty8</password>
            </connector>
        </connectors>
    </configuration>
</plugin>

To generate (or regenerate) the certificate, run mvn keytool:clean keytool:generateKeyPair.

With this approach, I just need to add an exception ONCE in my browser regardless of the number of times I restart Jetty… or when I’m still actively working on this project after 99999 days.

Maven: Deploying Java Sources to Nexus

By default, mvn deploy will only package and deploy class JAR to Nexus. To deploy the source code too, we need to add the following plugin into pom.xml:-

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.2.1</version>
    <executions>
        <execution>
            <id>attach-sources</id>
            <goals>
                <goal>jar</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Now, mvn deploy will deploy both class JAR and source code JAR to Nexus:-

In IntelliJ, when jumping into a class from the JAR, we are given the option to “Download Sources” or “Attach Sources…”.

For some reason, in order for “Download Sources” to work, we need to uncheck “Use Maven3 to import project”.

Now, when we click on “Download Sources”, it will download the sources directly from Nexus and display the source code.

Maven JAR Plugin: Excluding Package(s) from JAR

Let’s assume we want to exclude com.choonchernlim.epicapp.scratch package when we create the JAR file.

Add the following in pom.xml:-

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <executions>
        <execution>
            <id>default-jar</id>
            <phase>package</phase>
            <goals>
                <goal>jar</goal>
            </goals>
            <configuration>
                <excludes>
                    <exclude>**/scratch/**</exclude>
                </excludes>
            </configuration>
        </execution>
    </executions>
</plugin>

The key here is to specify the execution ID called default-jar.

Spring Security: Handling 403 Error Page

If you are already using Spring, then you might want to use Spring Security to secure your web resources.

To do that, we specify the URI to be secured with <security:intercept-url/> tag:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

When users without role ROLE_TOPSECRET access /top-secrets/kfc-secret, they will see this default error page:-

This proves that Spring Security is doing its job. However, the default error page looks rather F.U.G.L.Y. Further, the error page may reveal too much information about the application server. The above error page shows the application runs on Jetty. If I’m a motherhacker, I would research all the possible vulnerabilities on this particular application server in attempt to hack it.

A better solution is to provide a friendly error page when the user access is denied. This can be done by specifying <security:access-denied-handler/> tag:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:access-denied-handler error-page="/error/access-denied"/>
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

Then, we create a simple error controller that returns the error page:-

@Controller
@RequestMapping(value = "/error")
public class ErrorController {
    @RequestMapping(value = "/access-denied", method = RequestMethod.GET)
    public String accessDenied() {
        return "error-access-denied";
    }
}

Now, the user will see this custom error page:-

This solution is better than the previous one. However, SiteMesh doesn’t have the opportunity to decorate this error page before it gets rendered.

To fix this, we can create a simple redirect to allow the request to make a full-round trip to the server so that SiteMesh can decorate the error page:-

@Controller
@RequestMapping(value = "/error")
public class ErrorController {
    @RequestMapping(value = "/router", method = RequestMethod.GET)
    public String errorRouter(@RequestParam("q") String resource) {
        return "redirect:/error/" + resource;
    }

    @RequestMapping(value = "/access-denied", method = RequestMethod.GET)
    public String accessDenied() {
        return "error-access-denied";
    }
}

Then, we tweak the Spring Security to use the error router URI:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:access-denied-handler error-page="/error/router?q=access-denied"/>
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

Now, the user will see this nice beautiful error page:-