📜 ⬆️ ⬇️

ContactManager, part 5. Add work via HTTPS

Before sending our REST service to free floating and making it publicly available, you need to take care of enhancing security and ensure operation via HTTPS. We use Tomcat 7 as the servlet container.

The order of action will be as follows:


We generate a security key

The keytool utility from the standard JRE delivery 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 %JAVA_HOME%/bin and launch 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 RSA, DSA, DES values
keystore - file path

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

Open the file %CATALINA_HOME%/conf/server.xml and find the commented out piece
  <!-- Define a SSL HTTP/1.1 Connector on port 8443 This connector uses the JSSE configuration, when using APR, the connector should be using the OpenSSL style configuration described in the APR documentation --> <!-- <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" /> --> 

Remove comments from the Connector element and add a couple of attributes for our key:
  <Connector port="8443" SSLEnabled="true" protocol="HTTP/1.1" maxThreads="150" scheme="https" secure="true" keystoreFile="c:\contactmanager.keystore" keystorePass="password" sslProtocol="TLS" /> 

keystorePass - the password that we entered when generating the key. Yes, it is stored in clear form. There are ways to solve this problem, but for now let's leave it that way. Actually everything can be 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" needs to be replaced 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 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 Tomkat.

Configuring Spring Security

Here, too, everything is quite simple. In the security.xml file, the attribute requires-channel="https" must be added to each of the critical URLs of the web service. It will look like this:
 <intercept-url pattern="/ws/index*" access="hasAnyRole('ROLE_USER','ROLE_ANONYMOUS')" requires-channel="https"/> <intercept-url pattern="/ws/add*" access="hasRole('ROLE_USER')" requires-channel="https"/> <intercept-url pattern="/ws/delete/*" access="hasRole('ROLE_ADMIN') " requires-channel="https"/> 

We are testing

We also hid the resource /ws/index for HTTPS , so we index_user1() try the index_user1() test. Error, which, however, is expected. The question is what kind of error and how to fix it. JUnit swears at the response curve
 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 is clear that this is not the case. We look at the log in the console, there 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 do not form a query in the test. Go to the builder MockHttpServletRequestBuilder and study the list of its methods, look for something related to security. Yeah, here 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; } 

Looks 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() 

Hurray, it works! Fine. Modify the rest of the WS tests in the same way. Now we are transferring authorization data over a secure connection and we can safely lay out our REST service outside. But this concerns only REST requests, the old Form-based authentication is not protected by us and remains a vulnerability. I propose to solve this problem on my own.

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

To be continued.

Source: https://habr.com/ru/post/174513/


All Articles