When building a system, developers usually disregard the security aspects. Security has been always something very important to worry about, but it’s attracting even higher concerns than before. Just this year we had a few cases like the Heartbleed Bug or the CelebrityGate scandal. This has nothing to do with the post, but are just examples that security really matters and we should be aware of it.
With the increasing popularity of REST services it makes sense that these need to be secured in some way. A couple of weeks ago, I had to integrate my client with a REST service behind https. I have never done it before and that’s the reason for this post. I have to confess that I’m no security expert myself, so please correct me if I write anything stupid.
The Setup
For this example I have used the following setup:
I’m not going into many details about SSL and TSL, so please check here for additional content. Note that TLS is the new name for SSL evolution. Sometimes there is confusion between the two and people often say SSL, but use the newest version of TSL. Keep that in mind.
Don’t forget to follow the instructions on the following page to setup SSL for Tomcat: SSL Configuration HOW-TO. This is needed for the server to present the client with a set of credentials, a Certificate, to secure the connection between server and client.
The Code
Service
Let’s create a simple Spring REST Service:
RestService.java
| @Controller @RequestMapping("/") public class RestService { @RequestMapping(method = RequestMethod.GET) @ResponseBody public String get() { return "Called the get Rest Service"; } } |
And we also need some wiring for this to work:
RestConfig.java
| @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.radcortez.rest.ssl") public class RestConfig {} |
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <servlet> <servlet-name>rest</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.radcortez.rest.ssl</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>rest</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>Rest Application</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <user-data-constraint> <!-- Needed for our application to respond to https requests --> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> </web-app> |
Please, note the elements security-constraint
, user-data-constraint
and <transport-guarantee>CONFIDENTIAL</transport-guarantee>
. These are needed to specify that the application requires a secure connection. Check Securing Web Applications for Java Applications.
Running the Service
Just deploy the application on the TomEE Server using your favourite IDE environment and access https://localhost:8443/
. You should get the following (you might need to accept the server certificate first):
Note that the browser protocol is https
and the port is 8443
(assuming that you kept the default settings in SSL Configuration HOW-TO.
Client
Now, if you try to call this REST service with a Java client, most likely you are going to get the following message and Exception (or similar):
Message: I/O error on GET request for “https://localhost:8443/”:sun.security.validator.ValidatorException:
Exception: 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
This happens because the running JDK does not have a valid certificate for your server. You can import it, and get rid of the problem, but let’s do something more interesting. We are going to programatically supply a trusted keystore with our server certificate.
This is especially useful if:
- you are running your code into multiple environments
- you don’t have to manually import the certificate into the JDK every time
- if you upgrade the JDK you have to remember about the certificates
- for some odd reason you don’t have access to the JDK itself to import the certificate
Let’s write some code:
RestClientConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | @Configuration @PropertySource("classpath:config.properties") public class RestClientConfig { @Bean public RestOperations restOperations(ClientHttpRequestFactory clientHttpRequestFactory) throws Exception { return new RestTemplate(clientHttpRequestFactory); } @Bean public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) { return new HttpComponentsClientHttpRequestFactory(httpClient); } @Bean public HttpClient httpClient(@Value("${keystore.file}") String file, @Value("${keystore.pass}") String password) throws Exception { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream instream = new FileInputStream(new File(file)); try { trustStore.load(instream, password.toCharArray()); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); return HttpClients.custom().setSSLSocketFactory(sslsf).build(); } @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } } |
Here we use Spring RestOperations interface which specified a basic set of RESTful operations. Next we use Apache HTTP Components SSLConnectionSocketFactory which gives us the ability to validate the identity of the server against a list of trusted certificates. The certificate is loaded from the same file used on the server by KeyStore.
RestServiceClientIT.java
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = RestClientConfig.class) public class RestServiceClientIT { @Autowired private RestOperations rest; @Test public void testRestRequest() throws Exception { ResponseEntity<String> response = rest.getForEntity("https://localhost:8443/", String.class); System.out.println("response = " + response); System.out.println("response.getBody() = " + response.getBody()); } } |
A simple test class. We also need a properties file with the keystore file location and password:
config.properties
| keystore.file=${user.home}/.keystore keystore.pass=changeit |
This should work fine if you used all the defaults.
Running the Test
If you now run the test which invokes the REST service within a Java client, you should get the following output:
Response: <200 OK,Called the get Rest Service,{Server=[Apache-Coyote/1.1], Cache-Control=[private], Expires=[Thu, 01 Jan 1970 01:00:00 WET], Content-Type=[text/plain;charset=ISO-8859-1], Content-Length=[27], Date=[Tue, 23 Dec 2014 01:29:20 GMT]}>
Body: Called the get Rest Service
Conclusion
That’s it! You can now call your REST service with your client in a secured manner. If you prefer to add the certificate to the JDK keystore, please check this post.
Stay tuned for an equivalent for Java EE JAX-RS equivalent.
Resources
You can clone a full working copy from my github repository: REST SSL.
(TL;DR – Get me to the
code)
For a long time, the Java EE specification was lacking a Batch Processing API. Today, this is an essential necessity for enterprise applications. This was finally fixed with the JSR-352 Batch Applications for the Java Platform now available in Java EE 7. The JSR-352 got it’s inspiration from the Spring Batch counterpart. Both cover the same concepts, although the resulting API’s are a bit different.
Since the Spring team also collaborated in the JSR-352, it was only a matter of time for them to provide an implementation based on Spring Batch. The latest major version of Spring Batch (version 3), now supports the JSR-352.
I’m a Spring Batch user for many years and I’ve always enjoyed that the technology had a interesting set of built-in readers and writers. These allowed you to perform the most common operations required by batch processing. Do you need to read data from a database? You could use JdbcCursorItemReader
, how about writing data in a fixed format? Use FlatFileItemWriter
, and so on.
Unfortunately, JSR-352 implementations do not have the amount of readers and writers available in Spring Batch. We have to remember that JSR-352 is very recent and didn’t have time to catch up. jBeret, the Wildfly implementation for JSR-352 already provides a few custom readers and writers.
What’s the point?
I was hoping that with the latest release, all the readers and writers from the original Spring Batch would be available as well. This is not the case yet, since it would take a lot of work, but there are plans to make them available in future versions. This would allow us to migrate native Spring Batch applications into JSR-352. We still have the issue of the implementation vendor lock-in, but it may be interesting in some cases.
Motivation
I’m one of the main test contributors for the Java EE Samples in the JSR-352 specification. I wanted to find out if the tests I’ve implemented have the same behaviour using the Spring Batch implementation. How can we do that?
Code
I think this exercise is not only interesting because of the original motivation, but it’s also useful to learn about modules and class loading on Wildfly. First we need to decide how are we going to deploy the needed Spring Batch dependencies. We could deploy them directly with the application, or use a Wildfly module. Modules have the advantage to be bundled directly into the application server and can be reused by all deployed applications.
Adding Wildfly Module with Maven
With a bit of work it’s possible to add the module automatically with the Wildfly Maven Plugin and the CLI (command line). Let’s start to create two files that represent the CLI commands that we need to create and remove the module:
wildfly-add-spring-batch.cli
| # Connect to Wildfly instance connect # Create Spring Batch Module # If the module already exists, Wildfly will output a message saying that the module already exists and the script exits. module add \ --name=org.springframework.batch \ --dependencies=javax.api,javaee.api \ --resources=${wildfly.module.classpath} |
The module --name
is important. We’re going to need it to reference it in our application. The --resources
is a pain, since you need to indicate a full classpath to all the required module dependencies, but we’re generating the paths in the next few steps.
wildfly-remove-spring-batch.cli
| # Connect to Wildfly instance connect # Remove Oracle JDBC Driver Module module remove --name=org.springframework.batch |
Note wildfly.module.classpath
. This property will hold the complete classpath for the required Spring Batch dependencies. We can generate it with Maven Dependency plugin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>${version.plugin.dependency}</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>build-classpath</goal> </goals> <configuration> <outputProperty>wildfly.module.classpath</outputProperty> <pathSeparator>:</pathSeparator> <excludeGroupIds>javax</excludeGroupIds> <excludeScope>test</excludeScope> <includeScope>provided</includeScope> </configuration> </execution> </executions> </plugin> |
This is going to pick all dependencies (including transitive), exclude javax
(since they are already present in Wildfly) and exclude test
scope dependencies. We need the following dependencies for Spring Batch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <!-- Needed for Wildfly module --> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-core</artifactId> <version>3.0.0.RELEASE</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.0.5.RELEASE</version> <scope>provided</scope> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.2</version> <scope>provided</scope> </dependency> |
Now, we need to replace the property in the file. Let’s use Maven Resources plugin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>${version.plugin.resources}</version> <executions> <execution> <id>copy-resources</id> <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target/scripts</outputDirectory> <resources> <resource> <directory>src/main/resources/scripts</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> |
This will filter the configured files and replace the property wildfly.module.classpath
with the value we generated previously. This is a classpath pointing to the dependencies in your local Maven repository. Now with Wildfly Maven Plugin we can execute this script (you need to have Wildfly running):
| <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>${version.plugin.wildfly}</version> <configuration> <skip>false</skip> <executeCommands> <batch>false</batch> <scripts> <!--suppress MavenModelInspection --> <script>target/scripts/${cli.file}</script> </scripts> </executeCommands> </configuration> </plugin> |
And these profiles:
| <profiles> <profile> <id>install-spring-batch</id> <properties> <cli.file>wildfly-add-spring-batch.cli</cli.file> </properties> </profile> <profile> <id>remove-spring-batch</id> <properties> <cli.file>wildfly-remove-spring-batch.cli</cli.file> </properties> </profile> </profiles> |
(For the full pom.xml
contents, check here)
We can add the module by executing:
mvn process-resources wildfly:execute-commands -P install-spring-batch
.
Or remove the module by executing:
mvn wildfly:execute-commands -P remove-spring-batch
.
This strategy works for any module that you want to create into Wildfly. Think about adding a JDBC driver. You usually use a module to add it into the server, but all the documentation I’ve found about this is always a manual process. This works great for CI builds, so you can have everything you need to setup your environment.
Use Spring-Batch
Ok, I have my module there, but how can I instruct Wildfly to use it instead of jBeret? We need to add a the following file in META-INF
folder of our application:
jboss-deployment-structure.xml
| <?xml version="1.0" encoding="UTF-8"?> <jboss-deployment-structure> <deployment> <exclusions> <module name="org.wildfly.jberet"/> <module name="org.jberet.jberet-core"/> </exclusions> <dependencies> <module name="org.springframework.batch" services="import" meta-inf="import"/> </dependencies> </deployment> </jboss-deployment-structure> |
Since the JSR-352 uses a Service Loader to load the implementation, the only possible outcome would be to load the service specified in org.springframework.batch
module. Your batch code will now run with the Spring Batch implementation.
Testing
The github repository code, has Arquillian sample tests that demonstrate the behaviour. Check the Resources section below.
Resources
You can clone a full working copy from my github repository. You can find instructions there to deploy it.
Wildfly – Spring Batch
Since I may modify the code in the future, you can download the original source of this post from the release 1.0. In alternative, clone the repo, and checkout the tag from release 1.0 with the following command: git checkout 1.0
.
Future
I’ve still need to apply this to the Java EE Samples. It’s on my TODO list.
Last Wednesday, 16 July 2014, the fifth meeting of Coimbra JUG was held on the Department of Informatics Engineering of the University of Coimbra, in Portugal. The attendance was very good, we had around 30 people to listen João Antunes talk about the Web Framework – Spring MVC. A very special thanks to João for taking the challenge and steer the session.
This topic continues a long list of Web Frameworks planned to be presented at Coimbra JUG. Have a look into the most wanted sessions pool, and cast your vote if you’re a member.
I did a quick introduction about Coimbra JUG for the newcomers and João dived right into the session about Spring MVC. Spring MVC is not exactly a new technology, but only one or two attendees were using it. The rest of the attendees seemed very curious and interested to learn about it.
Looking into Rebellabs – Java Tools and Technologies Landscape for 2014, Spring MVC appears at 1st place for the Web Frameworks in use with a 40% share. A huge percentage, which means that is very likely for you to como across with it in the future.
As always, we had surprises for the attendees: beer and chocolates, if you participated in the discussion. IntelliJ sponsored our event, by offering a free license to raffle among the attendees. Congratulations to João Baltazar for winning the license. Develop with pleasure! We also offered the book The Definitive Guide to HTML5 WebSocket courtesy of Kaazing, congratulations to Jessica Vicente. Finally, we gave away two Atlassian Stash t-shirs: “Just Do GIT” to Mário Homem and João Almeida.
Here are the materials for the session:
Enjoy!