Guide rapide d'utilisation de Cloud Foundry UAA

1. Vue d'ensemble

Cloud Foundry User Account and Authentication (CF UAA) est un service de gestion et d'autorisation des identités. Plus précisément, il s'agit d'un fournisseur OAuth 2.0 permettant l'authentification et l'émission de jetons aux applications clientes.

Dans ce tutoriel, nous allons couvrir les bases de la configuration d'un serveur CF UAA. Nous verrons ensuite comment l'utiliser pour protéger les applications Resource Server.

Mais avant, clarifions le rôle de UAA dans le cadre d'autorisation OAuth 2.0.

2. Cloud Foundry UAA et OAuth 2.0

Commençons par comprendre comment UAA est lié à la spécification OAuth 2.0.

La spécification OAuth 2.0 définit quatre participants qui peuvent se connecter les uns aux autres: un propriétaire de ressources, un serveur de ressources, un client et un serveur d'autorisation.

En tant que fournisseur OAuth 2.0, UAA joue le rôle de serveur d'autorisation. Cela signifie que son objectif principal est d'émettre des jetons d'accès pour les applications clientes et de valider ces jetons pour les serveurs de ressources .

Pour permettre l'interaction de ces participants, nous devons d'abord configurer un serveur UAA, puis implémenter deux autres applications: l'une en tant que client et l'autre en tant que serveur de ressources.

Nous utiliserons le flux d'octroi d' autorisation_code avec le client. Et nous utiliserons l'autorisation de jeton Bearer avec le serveur de ressources. Pour une prise de contact plus sûre et plus efficace, nous utiliserons des JWT signés comme jetons d'accès.

3. Configuration d'un serveur UAA

Tout d'abord, nous installerons UAA et le remplirons avec des données de démonstration.

Une fois installée, nous enregistrerons une application cliente nommée webappclient. Ensuite, nous allons créer un utilisateur nommé appuser avec deux rôles, resource.read et resource.write .

3.1. Installation

UAA est une application Web Java qui peut être exécutée dans n'importe quel conteneur de servlet conforme. Dans ce tutoriel, nous utiliserons Tomcat.

Allons-y, téléchargeons la guerre UAA et déposons-la dans notre déploiement Tomcat :

wget -O $CATALINA_HOME/webapps/uaa.war \ //search.maven.org/remotecontent?filepath=org/cloudfoundry/identity/cloudfoundry-identity-uaa/4.27.0/cloudfoundry-identity-uaa-4.27.0.war

Avant de le démarrer, nous devrons toutefois configurer sa source de données et sa paire de clés JWS.

3.2. Configuration requise

Par défaut, UAA lit la configuration depuis uaa.yml sur son chemin de classe . Mais, puisque nous venons de télécharger le fichier war , il vaudra mieux que nous indiquions à UAA un emplacement personnalisé sur notre système de fichiers.

Nous pouvons le faire en définissant la propriété UAA_CONFIG_PATH :

export UAA_CONFIG_PATH=~/.uaa

Alternativement, nous pouvons définir CLOUD_FOUNDRY_CONFIG_PATH. Ou, nous pouvons spécifier un emplacement distant avec UAA_CONFIG_URL.

Ensuite, nous pouvons copier la configuration requise de UAA dans notre chemin de configuration:

wget -qO- //raw.githubusercontent.com/cloudfoundry/uaa/4.27.0/uaa/src/main/resources/required_configuration.yml \ > $UAA_CONFIG_PATH/uaa.yml

Notez que nous supprimons les trois dernières lignes car nous allons les remplacer dans un instant.

3.3. Configuration de la source de données

Alors, configurons la source de données, où UAA va stocker les informations sur les clients.

Pour les besoins de ce tutoriel, nous allons utiliser HSQLDB:

export SPRING_PROFILES="default,hsqldb"

Bien sûr, puisqu'il s'agit d'une application Spring Boot, nous pourrions également le spécifier dans uaa.yml en tant que propriété spring.profiles .

3.4. Configuration de la paire de clés JWS

Puisque nous utilisons JWT, UAA a besoin d'une clé privée pour signer chaque JWT émis par UAA.

OpenSSL rend cela simple:

openssl genrsa -out signingkey.pem 2048 openssl rsa -in signingkey.pem -pubout -out verificationkey.pem

Le serveur d'autorisation signera le JWT avec la clé privée, et notre client et serveur de ressources vérifiera cette signature avec la clé publique.

Nous les exporterons vers JWT_TOKEN_SIGNING_KEY et JWT_TOKEN_VERIFICATION_KEY :

export JWT_TOKEN_SIGNING_KEY=$(cat signingkey.pem) export JWT_TOKEN_VERIFICATION_KEY=$(cat verificationkey.pem) 

Encore une fois, nous pourrions les spécifier dans uaa.yml via les propriétés jwt.token.signing-key et jwt.token.verification-key .

3.5. Démarrage de UAA

Enfin, commençons les choses:

$CATALINA_HOME/bin/catalina.sh run

À ce stade, nous devrions avoir un serveur UAA fonctionnel disponible sur // localhost: 8080 / uaa .

Si nous allons à // localhost: 8080 / uaa / info , nous verrons quelques informations de démarrage de base

3.6. Installation du client de ligne de commande UAA

Le client de ligne de commande CF UAA est l'outil principal pour administrer UAA , mais pour l'utiliser, nous devons d'abord installer Ruby:

sudo apt install rubygems gem install cf-uaac

Then, we can configure uaac to point to our running instance of UAA:

uaac target //localhost:8080/uaa

Note that if we don't want to use command-line client, we can, of course, use UAA's HTTP client.

3.7. Populating Clients and Users Using UAAC

Now that we have uaac installed, let's populate UAA with some demo data. At a minimum, we'll need: A client, a user, and resource.read and resource.write groups.

So, to do any administration, we'll need to authentication ourselves. We'll pick the default admin that ships with UAA, which has permissions to create other clients, users, and groups:

uaac token client get admin -s adminsecret

(Of course, we definitely need to change this account – via the oauth-clients.xml file – before shipping!)

Basically, we can read this command as: “Give me a token, using client credentials with the client_id of admin and a secret of adminsecret“.

If all goes well, we'll see a success message:

Successfully fetched token via client credentials grant.

The token is stored in uaac‘s state.

Now, operating as admin, we can register a client named webappclient with client add:

uaac client add webappclient -s webappclientsecret \ --name WebAppClient \ --scope resource.read,resource.write,openid,profile,email,address,phone \ --authorized_grant_types authorization_code,refresh_token,client_credentials,password \ --authorities uaa.resource \ --redirect_uri //localhost:8081/login/oauth2/code/uaa

And also, we can register a user named appuser with user add:

uaac user add appuser -p appusersecret --emails [email protected]

Next, we'll add two groups – resource.read and resource.write – using with group add:

uaac group add resource.read uaac group add resource.write

And finally, we'll assign these groups to appuser with member add:

uaac member add resource.read appuser uaac member add resource.write appuser

Phew! So, what we've done so far is:

  • Installed and configured UAA
  • Installed uaac
  • Added a demo client, users, and groups

So, let's keep in mind these pieces of information and jump to the next step.

4. OAuth 2.0 Client

In this section, we'll use Spring Boot to create an OAuth 2.0 Client application.

4.1. Application Setup

Let's start by accessing Spring Initializr and generating a Spring Boot web application. We choose only the Web and OAuth2 Client components:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-oauth2-client 

In this example, we've used version 2.1.3 of Spring Boot.

Next, we need to register our client, webappclient.

Quite simply, we'll need to give the app the client-id, client-secret, and UAA's issuer-uri. We'll also specify the OAuth 2.0 scopes that this client wants the user to grant to it:

#registration spring.security.oauth2.client.registration.uaa.client-id=webappclient spring.security.oauth2.client.registration.uaa.client-secret=webappclientsecret spring.security.oauth2.client.registration.uaa.scope=resource.read,resource.write,openid,profile #provider spring.security.oauth2.client.provider.uaa.issuer-uri=//localhost:8080/uaa/oauth/token

For more information about these properties, we can have a look at the Java docs for the registration and provider beans.

And since we're already using port 8080 for UAA, let's have this run on 8081:

server.port=8081

4.2. Login

Now if we access the /login path, we should have a list of all registered clients. In our case, we have only one registered client:

Clicking on the link will redirect us to the UAA login page:

Here, let's login with appuser/appusersecret.

Submitting the form should redirect us to an approval form where the user can authorize or deny access to our client:

The user can then grant which privileges she wants. For our purposes, we'll select everything except resource:write.

Whatever the user checks will be the scopes in the resulting access token.

To prove this, we can copy the token shown at the index path, //localhost:8081, and decode it using the JWT debugger. We should see the scopes we checked on the approval page:

{ "jti": "f228d8d7486942089ff7b892c796d3ac", "sub": "0e6101d8-d14b-49c5-8c33-fc12d8d1cc7d", "scope": [ "resource.read", "openid", "profile" ], "client_id": "webappclient" // more claims }

Once our client application receives this token, it can authenticate the user and they'll have access to the app.

Now, an app that doesn't show any data isn't very useful, so our next step will be to stand up a resource server – which has the user's data – and connect the client to it.

The completed resource server will have two protected APIs: one that requires the resource.read scope and another that requires resource.write.

What we'll see is that the client, using the scopes we granted, will be able to call the read API but not write.

5. Resource Server

The resource server hosts the user's protected resources.

It authenticates clients via the Authorization header and in consultation with an authorization server – in our case, that's UAA.

5.1. Application Set Up

To create our resource server, we'll use Spring Initializr again to generate a Spring Boot web application. This time, we'll choose the Web and OAuth2 Resource Server components:

 org.springframework.boot spring-boot-starter-oauth2-resource-server   org.springframework.boot spring-boot-starter-web 

As with the Client application, we're using the version 2.1.3 of Spring Boot.

The next step is to indicate the location of the running CF UAA in the application.properties file:

spring.security.oauth2.resourceserver.jwt.issuer-uri=//localhost:8080/uaa/oauth/token

Of course, let's pick a new port here, too. 8082 will work fine:

server.port=8082

And that's it! We should have a working resource server and by default, all requests will require a valid access token in the Authorization header.

5.2. Protecting Resource Server APIs

Next, let's add some endpoints worth protecting, though.

We'll add a RestController with two endpoints, one authorized for users having the resource.read scope and the other for users having the resource.write scope:

@GetMapping("/read") public String read(Principal principal) { return "Hello write: " + principal.getName(); } @GetMapping("/write") public String write(Principal principal) { return "Hello write: " + principal.getName(); }

Next, we'll override the default Spring Boot configuration to protect the two resources:

@EnableWebSecurity public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/read/**").hasAuthority("SCOPE_resource.read") .antMatchers("/write/**").hasAuthority("SCOPE_resource.write") .anyRequest().authenticated() .and() .oauth2ResourceServer().jwt(); } }

Note that the scopes supplied in the access token are prefixed with SCOPE_ when they are translated to a Spring Security GrantedAuthority.

5.3. Requesting a Protected Resource From a Client

From the Client application, we'll call the two protected resources using RestTemplate. Before making the request, we retrieve the access token from the context and add it to the Authorization header:

private String callResourceServer(OAuth2AuthenticationToken authenticationToken, String url) { OAuth2AuthorizedClient oAuth2AuthorizedClient = this.authorizedClientService. loadAuthorizedClient(authenticationToken.getAuthorizedClientRegistrationId(), authenticationToken.getName()); OAuth2AccessToken oAuth2AccessToken = oAuth2AuthorizedClient.getAccessToken(); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Bearer " + oAuth2AccessToken.getTokenValue()); // call resource endpoint return response; }

Note, though, that we can remove this boilerplate if we use WebClient instead of RestTemplate.

Then, we'll add two calls to the resource server endpoints:

@GetMapping("/read") public String read(OAuth2AuthenticationToken authenticationToken) { String url = remoteResourceServer + "/read"; return callResourceServer(authenticationToken, url); } @GetMapping("/write") public String write(OAuth2AuthenticationToken authenticationToken) { String url = remoteResourceServer + "/write"; return callResourceServer(authenticationToken, url); }

As expected, the call of the /read API will succeed, but not the /write one. The HTTP status 403 tells us that the user is not authorized.

6. Conclusion

Dans cet article, nous avons commencé par un bref aperçu d'OAuth 2.0 car il s'agit de la base de base de UAA, un serveur d'autorisation OAuth 2.0. Ensuite, nous l'avons configuré pour émettre des jetons d'accès pour un client et sécuriser une application de serveur de ressources.

Le code source complet des exemples est disponible à l'adresse over sur Github.