X.509 Authentication in Spring Security

1. Overview

In this article, we’ll focus on the main use cases for X.509 certificate authenticationverifying the identity of a communication peer when using the HTTPS (HTTP over SSL) protocol.

Simply put – while a secure connection is established, the client verifies the server according to its certificate (issued by a trusted certificate authority).

But beyond that, X.509 in Spring Security can be used to verify the identity of a client through the server while connecting. This is called “mutual authentication, “ and we’ll look at how that’s done here as well.

Finally, we’ll touch on when it makes sense to use this kind of authentication.

To demonstrate server verification, we’ll create a simple web application and install a custom certificate authority in a browser.

And, for mutual authentication, we’ll create a client certificate and modify our server to allow only verified clients.

2. Keystores

Optional Requirement: To use cryptographically strong keys together with encryption and decryption features you need the ‘Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files’ installed in your JVM.

These can be downloaded for example from Oracle (follow the installation instructions included in the download). Some Linux distributions also provide an installable package through their package managers.

To implement X.509 authentication in a Spring application, we’ll first create a keystore in the Java Key-Store (JKS) format.

This keystore must contain a valid certificate of authority or a chain of certificate authorities and an own certificate for our server. The latter must be signed by one of the included authorities and has to be named after the hostname on which the server is running; we’ll use the Java keytool application here.

To simplify the process of creating keys and certificates using keytool, the code on Github, provides a commented Makefile for GNU make, containing all steps necessary to complete this section. You can also easily customize it via a few environment variables.

Tip: As an all-in-one step, you can run make without arguments. This will create a keystore, a truststore and two certificates for importing in your browser (one for localhost and one for a user called “cid”).

For creating a new keystore with a certificate authority, we can run make as follows:

$> make create-keystore PASSWORD=changeit

Now, we will add a certificate for our development host to this created keystore and sign it by our certificate authority:

$> make add-host HOSTNAME=localhost

To allow client authentication, we also need a keystore called “truststore”. This truststore has to contain valid certificates of our certificate authority and all of the allowed clients. For reference on using keytool, please look into the Makefile at the following given sections:

$> make create-truststore PASSWORD=changeit
$> make add-client CLIENTNAME=cid

3. Example Application

Our SSL secured server project will be consist of a @SpringBootApplication annotated application class (which is a kind of @Configuration), an application.properties configuration file and a very simple MVC-style front-end.

All, the application has to do, is presenting an HTML page with a “Hello {User}!” message. This way we can inspect the server certificate in a browser to make sure, that the connection is verified and secured.

First, we create a new Maven project with three Spring Boot Starter bundles included:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>1.4.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.4.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>1.4.0.RELEASE</version>
</dependency>

For reference: you will find the bundles on Maven Central (security, web, thymeleaf).

As the next step, we create the main application class and the user-controller:

@SpringBootApplication
public class X509AuthenticationServer {
    public static void main(String[] args) {
        SpringApplication.run(X509AuthenticationServer.class, args);
    }
}

@Controller
public class UserController {
    @RequestMapping(value = "/user")
    public String user(Model model, Principal principal) {

        UserDetails currentUser
          = (UserDetails) ((Authentication) principal).getPrincipal();
        model.addAttribute("username", currentUser.getUsername());
        return "user";
    }
}

Now, we tell the application where they can find our keystore and how it can be accessed. We set SSL to an “enabled” status and change the standard listening port to indicate a secured connection.

Additionally, we configure some user-details for accessing our server via Basic Authentication:

server.ssl.key-store=../keystore/keystore.jks
server.ssl.key-store-password=${PASSWORD}
server.ssl.key-alias=localhost
server.ssl.key-password=${PASSWORD}
server.ssl.enabled=true
server.port=8443
security.user.name=Admin
security.user.password=admin

This will be the HTML template, located at the resources/templates folder:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>X.509 Authentication Demo</title>
</head>
<body>
    <h2>Hello <span th:text="${username}"/>!</h2>
</body>
</html>

Before we finish this section and look at the site, we still need to install our generated certificate authority as trusted certificate in a browser of our choice.

An exemplary installation of our certificate authority for Mozilla Firefox would look like follows:

  1. Type about:preferences in the address bar

  2. Open Advanced → Certificates → View Certificates → Authorities

  3. Click on Import

  4. Locate the Baeldung tutorials folder and its subfolder spring-security-x509/keystore

  5. Select the ca.crt file and click OK

  6. Choose “Trust this CA to identify websites” and click OK

Note: If you don’t want to add our certificate authority to the list of trusted authorities, you’ll later have the option to make an exception and show the website tough, even when it is mentioned as insecure. But then you’ll see a ‘yellow exclamation mark’ symbol in the address bar, indicating the insecure connection!

Afterward, we will navigate to the spring-security-x509-basic-auth module and run:

mvn spring-boot:run

Finally, we hit https://localhost:8443/user, enter our user credentials from the application.properties and should see a “Hello Admin!” message. Now we’re able to inspect the connection status by clicking the ‘green lock’ symbol in the address bar, and it should be a secured connection.

image

 

4. Mutual Authentication

In this section, we use Spring Security to grant users access to our demo-website. The procedure makes a login form obsolete.

But before we continue to modify our server, we will discuss in short, when it makes sense to provide this kind of authentication.

Pros:

  • The private key of an X.509 client certificate is stronger than any user-defined password. But it has to be kept secret!

  • With a certificate, the identity of a client is well-known and easy to verify.

  • No more forgotten passwords!

Cons:

  • You must remember that for each user that should be verified by the server, its certificate needs to be installed in the configured truststore. For small applications with only a few clients, this may perhaps be practicable, with an increasing number of clients it may lead to complex key-management for users.

  • The private key of a certificate has to be installed in a client application. In fact: X.509 client authentication is device dependent, which makes it impossible to use this kind of authentication in public areas, for example in an internet-café.

  • There must be a mechanism to revoke compromised client certificates.

To continue, we are modifying our X509AuthenticationServer to extend from WebSecurityConfigurerAdapter and override one of the provided configure methods. Here we configure the x.509 mechanism to parse the Common Name (CN) field of a certificate for extracting usernames.

With this extracted usernames, Spring Security is looking up in a provided UserDetailsService for matching users. So we also implement this service interface containing one demo user.

Tip: In production environments, this UserDetailsService can load its users for example from a link:/spring-jdbc-jdbctemplate.

You have to notice that we annotate our class with @EnableWebSecurity and @EnableGlobalMethodSecurity with enabled pre-/post-authorization.

With the latter we can annotate our resources with @PreAuthorize and @PostAuthorize for a fine-grained access control:

@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter {
    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
          .and()
          .x509()
            .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
            .userDetailsService(userDetailsService());
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) {
                if (username.equals("cid")) {
                    return new User(username, "",
                      AuthorityUtils
                        .commaSeparatedStringToAuthorityList("ROLE_USER"));
                }
            }
        };
    }
}

As said previously, we are now able to use Expression-Based Access Control in our controller. More specifically, our authorization annotations are respected because of the @EnableGlobalMethodSecurity annotation in our @Configuration: _
_

@Controller
public class UserController {
    @PreAuthorize("hasAuthority('ROLE_USER')")
    @RequestMapping(value = "/user")
    public String user(Model model, Principal principal) {
        ...
    }
}

An overview over all possible authorization options can be found in the official documentation.

As final modification step, we have to tell the application where our truststore is located and that SSL client authentication is necessary (server.ssl.client-auth=need).

So we put the following into our application.properties:

server.ssl.trust-store=../keystore/truststore.jks
server.ssl.trust-store-password=${PASSWORD}
server.ssl.client-auth=need

Now, if we run the application and point our browser to https://localhost:8443/user, we become informed that the peer cannot be verified and it denies to open our website. So we also have to install our client certificate, which is outlined here exemplary for Mozilla Firefox:

  1. Type about:preferences in the address bar

  2. Open Advanced → View Certificates → Your Certificates

  3. Click on Import

  4. Locate the Baeldung tutorials folder and its subfolder spring-security-x509/keystore

  5. Select the cid.p12 file and click OK

  6. Input the password for your certificate and click OK

As a final step, we’re refreshing our browser-tab containing the website and selecting our client certificate in the newly opened chooser-dialog.

image

If we see a welcome message like “Hello cid!”, we were successful!

5. Mutual Authentication with XML

Adding X.509 client authentication to a http security configuration in XML is also possible:

<http>
    ...
    <x509 subject-principal-regex="CN=(.*?)(?:,|$)"
      user-service-ref="userService"/>

    <authentication-manager>
        <authentication-provider>
            <user-service id="userService">
                <user name="cid" password="" authorities="ROLE_USER"/>
            </user-service>
        </authentication-provider>
    </authentication-manager>
    ...
</http>

To configure an underlying Tomcat, we have to put our keystore and our truststore into its conf folder and edit the server.xml:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true"
    clientAuth="true" sslProtocol="TLS"
    keystoreFile="${catalina.home}/conf/keystore.jks"
    keystoreType="JKS" keystorePass="changeit"
    truststoreFile="${catalina.home}/conf/truststore.jks"
    truststoreType="JKS" truststorePass="changeit"
/>

Tip: With clientAuth set to “want”, SSL is still enabled, even if the client doesn’t provide a valid certificate. But in this case, we have to use a second authentication mechanism, for example, a login-form, to access the secured resources.

6. Conclusion

In summary, we’ve learned how to create a keystore containing a certificate authority and a self-signed certificate for our development environment.

We’ve created the truststore containing a certificate authority and a client certificate, and we have used both to verify our server on the client-side and our client on the server-side.

If you have studied the Makefile, you should be able to create certificates, make certificate-requests and import signed certificates using Java keytool.

Furthermore, you now should be able to export a client certificate into the PKCS12 format, and using it in a client application like a browser, for example, Mozilla Firefox.

And we’ve discussed when it makes sense to use Spring Security X.509 client authentication, so it is up to you, to decide, whether to implement it into your web application, or not.

And to wrap up, you’ll find the source code to this article on Github.

Leave a Reply

Your email address will not be published.