Authentification X.509 dans Spring Security

1. Vue d'ensemble

Dans cet article, nous nous concentrerons sur les principaux cas d'utilisation de l'authentification par certificat X.509 - vérifier l'identité d'un homologue de communication lors de l'utilisation du protocole HTTPS (HTTP sur SSL).

En termes simples, pendant qu'une connexion sécurisée est établie, le client vérifie le serveur en fonction de son certificat (émis par une autorité de certification de confiance).

Mais au-delà de cela, X.509 dans Spring Security peut être utilisé pour vérifier l'identité d'un client par le serveur lors de la connexion. C'est ce qu'on appelle «l'authentification mutuelle», et nous verrons comment cela se fait ici également.

Enfin, nous aborderons quand il est judicieux d'utiliser ce type d'authentification .

Pour démontrer la vérification du serveur, nous allons créer une application Web simple et installer une autorité de certification personnalisée dans un navigateur.

De plus, pour l'authentification mutuelle , nous créerons un certificat client et modifierons notre serveur pour n'autoriser que les clients vérifiés.

Il est fortement recommandé de suivre le didacticiel étape par étape et de créer vous-même les certificats, ainsi que le keystore et le truststore, selon les instructions présentées dans les sections suivantes. Cependant, tous les fichiers prêts à l'emploi se trouvent dans notre référentiel GitHub.

2. Autorité de certification racine auto-signée

Pour pouvoir signer nos certificats côté serveur et côté client, nous devons d'abord créer notre propre certificat d'autorité de certification racine auto-signé. De cette façon, nous agirons comme notre propre autorité de certification .

Pour cela, nous utiliserons la bibliothèque openssl, nous devons donc l'avoir installée avant de passer à l'étape suivante.

Créons maintenant le certificat CA:

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

Lorsque nous exécutons la commande ci-dessus, nous devons fournir le mot de passe de notre clé privée. Pour les besoins de ce didacticiel, nous utilisons changeit comme phrase de passe.

De plus, nous devons entrer des informations qui forment un soi-disant nom distinctif . Ici, nous ne fournissons que le CN (nom commun) - Baeldung.com - et laissons les autres parties vides.

3. Keystore

Condition facultative : pour utiliser des clés cryptographiquement fortes avec des fonctionnalités de cryptage et de décryptage, nous aurons besoin des « Fichiers de stratégie de juridiction de force illimitée Java Cryptography Extension (JCE) » installés dans notre JVM .

Ceux-ci peuvent être téléchargés par exemple à partir d'Oracle (suivez les instructions d'installation incluses dans le téléchargement). Certaines distributions Linux fournissent également un package installable via leurs gestionnaires de packages.

Un keystore est un référentiel que notre application Spring Boot utilisera pour contenir la clé privée et le certificat de notre serveur. En d'autres termes, notre application utilisera le keystore pour servir le certificat aux clients lors de la négociation SSL.

Dans ce didacticiel, nous utilisons le format Java Key-Store (JKS) et un outil de ligne de commande keytool.

3.1. Certificat côté serveur

Pour implémenter l'authentification X.509 côté serveur dans notre application Spring Boot, nous devons d'abord créer un certificat côté serveur.

Commençons par créer une soi-disant demande de signature de certificat (CSR):

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

De même, comme pour le certificat CA, nous devons fournir le mot de passe de la clé privée. De plus, utilisons localhost comme nom commun (CN).

Avant de continuer, nous devons créer un fichier de configuration - localhost.ext . Il stockera certains paramètres supplémentaires nécessaires lors de la signature du certificat.

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

Un fichier prêt à l'emploi est également disponible ici.

Maintenant, il est temps de signer la requête avec notre certificat rootCA.crt et sa clé privée :

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

Notez que nous devons fournir le même mot de passe que celui utilisé lors de la création de notre certificat CA.

À ce stade, nous avons enfin un certificat localhost.crt prêt à l'emploi signé par notre propre autorité de certification.

Pour imprimer les détails de notre certificat sous une forme lisible par l'homme, nous pouvons utiliser la commande suivante:

openssl x509 -in localhost.crt -text

3.2. Importer dans le keystore

Dans cette section, nous verrons comment importer le certificat signé et la clé privée correspondante dans le fichier keystore.jks .

Nous utiliserons l'archive PKCS 12 pour empaqueter la clé privée de notre serveur avec le certificat signé. Ensuite, nous l'importerons dans le fichier keystore.jks nouvellement créé .

Nous pouvons utiliser la commande suivante pour créer un fichier .p12 :

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

Nous avons donc maintenant le localhost.key et le localhost.crt regroupés dans le seul fichier localhost.p12 .

Utilisons maintenant keytool pour créer un référentiel keystore.jks et importer le fichier localhost.p12 avec une seule commande :

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

À ce stade, nous avons tout en place pour la partie authentification du serveur. Continuons avec la configuration de notre application Spring Boot.

4. Exemple d'application

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.