ContactManager, part 5. Add work via HTTPS

  • Tutorial
Before sending our REST service for free swimming and making it publicly available, you need to take care of strengthening security and ensuring work through HTTPS. We use Tomcat 7 as the servlet container.

The procedure is as follows:
  • generate security key
  • add support for HTTS in Tomcat
  • add HTTS support in SpringSecurity
  • to test (and how without it)


Generate Security Key

The keytool utility from the standard JRE package will help us generate the key. If JAVA_HOME is added to path, then just run keytool from the command line, if not, then go to the directory %JAVA_HOME%/binand run keytool from there. For MS Windows, the command will look something like this:
keytool -genkey -alias ContactManager -keyalg RSA -keystore c:/contactmanager.keystore

alias- unique key identifier
keyalg- generation algorithm. Possible values ​​of RSA, DSA, DES
keystore- the path to the file.

After starting the program will ask you to enter a password and several parameters, it is advisable to remember the password, it will still be useful to us, other values ​​can be arbitrary: who, what, where, country, etc. As a result, we get the file on disk in the specified directory. The key is ready.

Change Tomcat settings

Open the file %CATALINA_HOME%/conf/server.xmland find the commented out piece.
    
    
    -->

We remove the comments from the Connector element and add a couple of attributes for our key:

keystorePass- the password that we entered when generating the key. Yes, it is kept open. There are ways to solve this problem, but for now let’s leave it this way. Actually everything, you can run. Oops ...
INFO: Initializing ProtocolHandler ["http-apr-8080"]
мар 28, 2013 11:43:04 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-apr-8443"]
мар 28, 2013 11:43:04 AM org.apache.coyote.AbstractProtocol init
SEVERE: Failed to initialize end point associated with ProtocolHandler ["http-apr-8443"]
java.lang.Exception: Connector attribute SSLCertificateFile must be defined when using SSL with APR
        at org.apache.tomcat.util.net.AprEndpoint.bind(AprEndpoint.java:507)
        ...

Did not work out. Googling gives the answer that protocol="HTTP/1.1"you need to replace with protocol="org.apache.coyote.http11.Http11Protocol". We start, now everything is in order.
...
мар 28, 2013 11:56:41 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-apr-8080"]
мар 28, 2013 11:56:41 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8443"]
мар 28, 2013 11:56:41 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["ajp-apr-8009"]
мар 28, 2013 11:56:41 AM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 1909 ms
...

When you go to the address https: // localhost: 8443 / the browser warns about the doubtfulness of our certificate, but we ignore its warnings, click "continue at your own risk" and see the root page of Tomcat.

Configure Spring Security

Here, too, everything is quite simple. In the security.xml file, an attribute must be added to each of the critical URLs of the web service requires-channel="https". It will look like this:

Testing

/ws/indexWe also hid the resource behind HTTPS, so we will try to run the test index_user1(). Error, which, however, is expected. The question is what kind of error and how to fix it. JUnit swears on a curve answer
com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input
 at [Source: java.io.StringReader@1841d1d3; line: 1, column: 1]

but it’s clear that this is not the case. We look at the log in the console, there it is already more interesting, there is an error status, 302:
...
MockHttpServletResponse:
              Status = 302
       Error message = null
             Headers = {Location=[https://localhost/ws/index]}
        Content type = null
                Body = 
       Forwarded URL = null
      Redirected URL = https://localhost/ws/index
             Cookies = []

Apparently, we somehow form the request in the test in a wrong way. We go to the MockHttpServletRequestBuilder builder and study the list of its methods, look for something related to security. Yeah, there it is.
	/**
	 * Set the secure property of the {@link ServletRequest} indicating use of a
	 * secure channel, such as HTTPS.
	 *
	 * @param secure whether the request is using a secure channel
	 */
	public MockHttpServletRequestBuilder secure(boolean secure){
		this.secure = secure;
		return this;
	}

It seems like what you need. Add this method to the call chain in the builder.
		def result = mockMvc.perform(MockMvcRequestBuilders.get("/ws/index")
				.secure(true) // <--------- добавляем работу через HTTPS
				.with(SecurityRequestPostProcessors.userDetailsService(USER1)))
				.andDo(MockMvcResultHandlers.print())
				.andReturn()

Hooray, it works! Excellent. We modify the remaining WS-tests in the same way. Now we transfer authorization data over a secure connection and we can safely lay out our REST service outside. But this applies only to REST requests, the old Form-based authentication is not protected in any way and remains a weak point. I propose to solve this problem myself.

What more can be done? Now we are forced to provide a username and password for every request to a secure resource. Plus, users are rigidly registered in the seciruty.xml file, and suddenly (although why suddenly?) Our service will become popular? Therefore, in the next iteration, we will do the following: transfer the user information to the database and change the authentication scheme to work with Auth Token, in which we will store data about the user's session.

To be continued.

Also popular now: