X.509-Authentifizierung in Spring Security

1. Übersicht

In diesem Artikel konzentrieren wir uns auf die Hauptanwendungsfälle für die X.509-Zertifikatauthentifizierung - Überprüfung der Identität eines Kommunikations-Peers bei Verwendung des HTTPS-Protokolls (HTTP over SSL).

Einfach ausgedrückt: Während eine sichere Verbindung hergestellt wird, überprüft der Client den Server anhand seines Zertifikats (ausgestellt von einer vertrauenswürdigen Zertifizierungsstelle).

Darüber hinaus kann X.509 in Spring Security verwendet werden, um die Identität eines Clients durch den Server während der Verbindung zu überprüfen . Dies wird als "gegenseitige Authentifizierung" bezeichnet, und wir werden uns auch hier ansehen, wie dies getan wird.

Abschließend werden wir darauf eingehen, wann es sinnvoll ist, diese Art der Authentifizierung zu verwenden .

Um die Serverüberprüfung zu demonstrieren, erstellen wir eine einfache Webanwendung und installieren eine benutzerdefinierte Zertifizierungsstelle in einem Browser.

Darüber hinaus erstellen wir zur gegenseitigen Authentifizierung ein Client-Zertifikat und ändern unseren Server so, dass nur verifizierte Clients zugelassen werden.

Es wird dringend empfohlen, das Lernprogramm Schritt für Schritt zu befolgen und die Zertifikate sowie den Keystore und den Truststore selbst gemäß den Anweisungen in den folgenden Abschnitten zu erstellen. Alle gebrauchsfertigen Dateien finden Sie jedoch in unserem GitHub-Repository.

2. Selbstsignierte Stammzertifizierungsstelle

Um unsere serverseitigen und clientseitigen Zertifikate signieren zu können, müssen wir zuerst unser eigenes selbstsigniertes Stammzertifizierungsstellenzertifikat erstellen. Auf diese Weise fungieren wir als unsere eigene Zertifizierungsstelle .

Zu diesem Zweck verwenden wir die openssl-Bibliothek, daher müssen wir sie installieren, bevor wir den nächsten Schritt ausführen können.

Lassen Sie uns nun das CA-Zertifikat erstellen:

openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout rootCA.key -out rootCA.crt

Wenn wir den obigen Befehl ausführen, müssen wir das Passwort für unseren privaten Schlüssel angeben. Für dieses Tutorial verwenden wir changeit als Passphrase.

Zusätzlich müssen wir Informationen eingeben, die einen sogenannten definierten Namen bilden . Hier geben wir nur den CN (Common Name) - Baeldung.com - an und lassen andere Teile leer.

3. Keystore

Optionale Anforderung : Um kryptografisch starke Schlüssel zusammen mit Verschlüsselungs- und Entschlüsselungsfunktionen verwenden zu können, benötigen wir die in unserer JVM installierten Richtliniendateien mit unbegrenzter Gerichtsbarkeit für Java Cryptography Extension (JCE) .

Diese können beispielsweise von Oracle heruntergeladen werden (befolgen Sie die im Download enthaltenen Installationsanweisungen). Einige Linux-Distributionen bieten über ihre Paketmanager auch ein installierbares Paket an.

Ein Keystore ist ein Repository, in dem unsere Spring Boot-Anwendung den privaten Schlüssel und das Zertifikat unseres Servers aufbewahrt. Mit anderen Worten, unsere Anwendung verwendet den Keystore, um das Zertifikat den Clients während des SSL-Handshakes bereitzustellen.

In diesem Tutorial verwenden wir das Java Key-Store-Format (JKS) und ein Keytool-Befehlszeilentool.

3.1. Serverseitiges Zertifikat

Um die serverseitige X.509-Authentifizierung in unserer Spring Boot-Anwendung zu implementieren, müssen Sie zunächst ein serverseitiges Zertifikat erstellen.

Beginnen wir mit der Erstellung einer sogenannten Certificate Signing Request (CSR):

openssl req -new -newkey rsa:4096 -keyout localhost.key –out localhost.csr

Ebenso müssen wir für das CA-Zertifikat das Kennwort für den privaten Schlüssel angeben. Verwenden wir außerdem localhost als allgemeinen Namen (Common Name, CN).

Bevor wir fortfahren, müssen wir eine Konfigurationsdatei erstellen - localhost.ext . Es werden einige zusätzliche Parameter gespeichert, die beim Signieren des Zertifikats benötigt werden.

authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE subjectAltName = @alt_names [alt_names] DNS.1 = localhost

Eine gebrauchsfertige Datei finden Sie auch hier.

Jetzt ist es Zeit, die Anfrage mit unserem rootCA.crt- Zertifikat und seinem privaten Schlüssel zu signieren :

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in localhost.csr -out localhost.crt -days 365 -CAcreateserial -extfile localhost.ext

Beachten Sie, dass wir dasselbe Kennwort angeben müssen, das wir beim Erstellen unseres CA-Zertifikats verwendet haben.

Zu diesem Zeitpunkt haben wir endlich ein gebrauchsfertiges localhost.crt- Zertifikat, das von unserer eigenen Zertifizierungsstelle signiert wurde.

Um die Details unseres Zertifikats in einer für Menschen lesbaren Form auszudrucken, können wir den folgenden Befehl verwenden:

openssl x509 -in localhost.crt -text

3.2. In den Keystore importieren

In diesem Abschnitt erfahren Sie, wie Sie das signierte Zertifikat und den entsprechenden privaten Schlüssel in die Datei keystore.jks importieren .

Wir werden das PKCS 12-Archiv verwenden, um den privaten Schlüssel unseres Servers zusammen mit dem signierten Zertifikat zu verpacken. Dann importieren wir es in die neu erstellte keystore.jks.

Wir können den folgenden Befehl verwenden, um eine .p12- Datei zu erstellen :

openssl pkcs12 -export -out localhost.p12 -name "localhost" -inkey localhost.key -in localhost.crt

So haben wir jetzt die localhost.key und die localhost.crt in der Einzel gebündelt localhost.p12 Datei.

Verwenden wir jetzt keytool, um ein keystore.jks- Repository zu erstellen und die Datei localhost.p12 mit einem einzigen Befehl zu importieren :

keytool -importkeystore -srckeystore localhost.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS

Zu diesem Zeitpunkt haben wir alles für den Serverauthentifizierungsteil eingerichtet. Fahren wir mit unserer Spring Boot-Anwendungskonfiguration fort.

4. Beispielanwendung

Our SSL secured server project consists 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 to present 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.

4.1. Maven Dependencies

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

 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf 

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

4.2. Spring Boot Application

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 to find our keystore.jks and how to access it. 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=../store/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 spring.security.user.name=Admin spring.security.user.password=admin

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

 X.509 Authentication Demo 

Hello !

4.3. Root CA Installation

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

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 rootCA.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 //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.

5. Mutual Authentication

In the previous section, we presented how to implement the most common SSL authentication schema – server-side authentication. This means, only a server authenticated itself to clients.

In this section, we'll describe how to add the other part of the authentication – client-side authentication. This way, only clients with valid certificates signed by the authority that our server trusts, can access our secured website.

But before we continue, let's see what are the pros and cons of using the mutual SSL 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:

  • We need to create a certificate for each new client.
  • The client's 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.
  • We must maintain the clients' certificates. This can easily become costly.

5.1. Truststore

A trustsore in some way is the opposite of a keystore. It holds the certificates of the external entities that we trust.

In our case, it's enough to keep the root CA certificate in the truststore.

Let's see how to create a truststore.jks file and import the rootCA.crt using keytool:

keytool -import -trustcacerts -noprompt -alias ca -ext san=dns:localhost,ip:127.0.0.1 -file rootCA.crt -keystore truststore.jks

Note, we need to provide the password for the newly created trusstore.jks. Here, we again used the changeit passphrase.

That's it, we've imported our own CA certificate, and the truststore is ready to be used.

5.2. Spring Security Configuration

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 fine-grained access control:

@SpringBootApplication @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class X509AuthenticationServer extends WebSecurityConfigurerAdapter { ... @Override protected void configure(HttpSecurity http) throws Exception $)") .userDetailsService(userDetailsService());  @Bean public UserDetailsService userDetailsService() { return new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) { if (username.equals("Bob")) { return new User(username, "", AuthorityUtils .commaSeparatedStringToAuthorityList("ROLE_USER")); } throw new UsernameNotFoundException("User not found!"); } }; } }

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 of all possible authorization options can be found in the official documentation.

As a 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=store/truststore.jks server.ssl.trust-store-password=${PASSWORD} server.ssl.client-auth=need

Now, if we run the application and point our browser to //localhost:8443/user, we become informed that the peer cannot be verified and it denies to open our website.

5.3. Client-side Certificate

Now it's time to create the client-side certificate. The steps we need to take, are pretty much the same as for the server-side certificate we already created.

First, we have to create a certificate signing request:

openssl req -new -newkey rsa:4096 -nodes -keyout clientBob.key –out clientBob.csr

We'll have to provide information that will be incorporated into the certificate. For this exercise, let's only enter the common name (CN) – Bob. It's important as we use this entry during the authorization and only Bob is recognized by our sample application.

Next, we need to sign the request with our CA:

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in clientBob.csr -out clientBob.crt -days 365 -CAcreateserial

The last step we need to take is to package the signed certificate and the private key into the PKCS file:

openssl pkcs12 -export -out clientBob.p12 -name "clientBob" -inkey clientBob.key -in clientBob.crt

Finally, we're ready to install the client certificate in the browser.

Again, we'll use 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/store
  5. Select the clientBob.p12 file and click OK
  6. Input the password for your certificate and click OK

Now, when we refresh our website, we'll be prompted to select the client certificate we'd like to use:

If we see a welcome message like “Hello Bob!”, that means everything works as expected!

6. Mutual Authentication With XML

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

 ...         ... 

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

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.

7. Conclusion

In summary, we've learned how to create a self-signed CA certificate and how to use it to sign other certificates.

Additionally, we've created both, server-side and client-side certificates. Then we've presented how to import them into a keystore and a truststore accordingly.

Furthermore, you now should be able to package a certificate together with its private key into the PKCS12 format.

We've also 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, find the source code to this article on Github.