The new Certification Class of Learn Spring Security is out:

>> CHECK OUT THE COURSE

1. Overview

In this article, we’ll focus on the main use cases for X.509 certificate authentication  – verifying 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 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 basic-secured-server 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.

 

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 JDBC Datasource.

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.

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.

Go deeper into Spring Security with the course:

>> LEARN SPRING SECURITY

  • Rudy Bonefas

    Once again, a very useful tutorial. I’d like to extend the mutual auth client certs as a pass through to my Zuul proxy. I have a web app where my many of my Ajax calls are routed through a Zuul Proxy. The server referenced by the proxy requires mutual authentication. Is there anyway I can forward the users PKI credentials from my web app to the proxy?

    • Hey Rudy – I haven’t ever looked into that aspect of Zuul – so I’m not sure – you’ll have to have a look at their docs.
      Cheers,
      Eugen.

  • daniel pr

    hi, how can i authenticate with smarth card certificate?

    • I’ve never done that myself, so I’m not sure if it’s something that the framework supports.
      Cheers,
      Eugen.

  • Karol

    Hi, I want to implement security with matching CN from cert, but only for specific url (let say /security/*) and other request should be accessible with non certyficate checking over http. Is it possible?

    Regards

    • Sure Karol – you can define multiple HTTP elements in your security configuration and set these up separately.

  • Logan

    Hi, I have a question about Mutual Authentication. Is it secure enough? Let’s suppose, that someone will get user cert (which is signed by CA) and generate his own cert. Will server accept that certyficate? Would be more secure to authorize user using their cert serial or fingerptint?

    • Hey Logan,
      “Secure enough” is entirely dependent on what you’re trying to do, but X.509 is certainly secure.
      Now, let’s clear up a couple of terms. Keep in mind that the public key is – well – public. It’s the private key that the client uses that shouldn’t be compromised. And that re-positions your questions about the server accepting the certificate – so it’s worth exploring that mechanism a bit first.
      And finally, I’m not sure what you mean by “fingerprint” (there are several ways to read that).
      Hope that points you in the right direction. Cheers,
      Eugen.

  • Charrad Nabil

    Hi, Do you know how I can recover CN, DN, expiration date, CA … of certificate?

    • I haven’t ever tried to do that programmatically. I imagine, once you have the actual certificate, you can inspect it and retrieve from it wherever you need. But again, I haven’t tried it.
      Cheers,
      Eugen.

  • Ian Pea

    To run through your example, http://www.baeldung.com/x-509-authentication-in-spring-security,
    is needed ca.crt ,
    said to be located in “Baeldung tutorials folder and its subfolder spring-security-x509/keystore”

    This ca.crt does not exist in this location !?

    best regards
    I.Pea

    • Grzegorz Piwowarek

      You need to generate this file. It’s covered in the section 2

      • Ian Pea

        I thought the example provided would be complete, and ready for study / analysis. A kind of quality garantee. There are so many half finnished examples out there, I thought this would be different.

        • Grzegorz Piwowarek

          Ok, thanks for feedback. Next time, we will take into consideration that some readers do not really want to go through whole articles but expect everything on a silver platter instead.

  • Miklos Toth

    Hi, thanks for this. I would like to ask. How does the system knows the username? because with the key the user does not send a username to the system, right?

    • Hey Miklos,
      You can specify the user/owner of the certificate via a regular expression – basically telling the framework how to extract it out of the certificate. Have a look here to get a bit more details.
      Cheers,
      Eugen.

      • Miklos Toth

        Thanks. It is clear now.