Una guida rapida all'uso di Cloud Foundry UAA

1. Panoramica

L'account utente e l'autenticazione di Cloud Foundry (CF UAA) è un servizio di gestione e autorizzazione delle identità. Più precisamente, è un provider OAuth 2.0 che consente l'autenticazione e l'emissione di token alle applicazioni client.

In questo tutorial, tratteremo le basi della configurazione di un server CF UAA. Vedremo quindi come usarlo per proteggere le applicazioni Resource Server.

Ma prima, chiariamo il ruolo della UAA nel framework di autorizzazione OAuth 2.0.

2. Cloud Foundry UAA e OAuth 2.0

Cominciamo col capire come la UAA si relaziona alla specifica OAuth 2.0.

La specifica OAuth 2.0 definisce quattro partecipanti che possono connettersi tra loro: un proprietario di risorse, un server di risorse, un client e un server di autorizzazione.

In qualità di provider OAuth 2.0, UAA svolge il ruolo di server di autorizzazione. Ciò significa che il suo obiettivo principale è l'emissione di token di accesso per le applicazioni client e la convalida di questi token per i server di risorse .

Per consentire l'interazione di questi partecipanti, dobbiamo prima configurare un server UAA e poi implementare altre due applicazioni: una come client e l'altra come server di risorse.

Useremo l'authorization_code flusso di sovvenzione con il cliente. E useremo l'autorizzazione del token Bearer con il server delle risorse. Per una stretta di mano più sicura ed efficiente, utilizzeremo JWT firmati come token di accesso.

3. Configurazione di un server UAA

Innanzitutto, installeremo UAA e lo popoleremo con alcuni dati demo.

Una volta installato, registreremo un'applicazione client denominata webappclient. Quindi, creeremo un utente denominato appuser con due ruoli, resource.read e resource.write .

3.1. Installazione

UAA è un'applicazione Web Java che può essere eseguita in qualsiasi contenitore servlet conforme. In questo tutorial, useremo Tomcat.

Andiamo avanti e scarichiamo la guerra UAA e depositiamola nella nostra distribuzione 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

Prima di avviarlo, però, dovremo configurare la sua origine dati e la coppia di chiavi JWS.

3.2. Configurazione richiesta

Per impostazione predefinita, UAA legge la configurazione da uaa.yml sul proprio classpath. Tuttavia, poiché abbiamo appena scaricato il file war , sarà meglio per noi indicare a UAA una posizione personalizzata nel nostro file system.

Possiamo farlo impostando la proprietà UAA_CONFIG_PATH :

export UAA_CONFIG_PATH=~/.uaa

In alternativa, possiamo impostare CLOUD_FOUNDRY_CONFIG_PATH. Oppure possiamo specificare una posizione remota con UAA_CONFIG_URL.

Quindi, possiamo copiare la configurazione richiesta da UAA nel nostro percorso di configurazione:

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

Nota che stiamo cancellando le ultime tre righe perché le sostituiremo tra un momento.

3.3. Configurazione dell'origine dati

Quindi, configuriamo l'origine dati, dove UAA memorizzerà le informazioni sui client.

Ai fini di questo tutorial, utilizzeremo HSQLDB:

export SPRING_PROFILES="default,hsqldb"

Ovviamente, poiché si tratta di un'applicazione Spring Boot, potremmo anche specificarlo in uaa.yml come proprietà spring.profiles .

3.4. Configurazione della coppia di chiavi JWS

Poiché utilizziamo JWT, UAA deve disporre di una chiave privata per firmare ogni JWT emesso da UAA.

OpenSSL lo rende semplice:

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

Il server di autorizzazione firmerà il JWT con la chiave privata e il nostro client e server di risorse verificherà quella firma con la chiave pubblica.

Li esporteremo in JWT_TOKEN_SIGNING_KEY e JWT_TOKEN_VERIFICATION_KEY :

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

Di nuovo, potremmo specificarli in uaa.yml tramite le proprietà jwt.token.signing-key e jwt.token.verification-key .

3.5. Avvio della UAA

Infine, iniziamo le cose:

$CATALINA_HOME/bin/catalina.sh run

A questo punto, dovremmo avere un server UAA funzionante disponibile su // localhost: 8080 / uaa .

Se andiamo a // localhost: 8080 / uaa / info , vedremo alcune informazioni di avvio di base

3.6. Installazione del client della riga di comando UAA

Il client della riga di comando CF UAA è lo strumento principale per l'amministrazione di UAA , ma per usarlo, dobbiamo prima installare 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

In questo articolo, abbiamo iniziato con una breve panoramica di OAuth 2.0 in quanto è la base di base per UAA, un server di autorizzazione OAuth 2.0. Quindi, lo abbiamo configurato per l'emissione di token di accesso per un client e la protezione di un'applicazione del server di risorse.

Il codice sorgente completo per gli esempi è disponibile su Github.