
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:
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
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.
Open the file
We remove the comments from the Connector element and add a couple of attributes for our key:
Did not work out. Googling gives the answer that
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.
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
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:
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.
It seems like what you need. Add this method to the call chain in the builder.
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.
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%/bin
and 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.xml
and 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/index
We 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 answercom.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.