Découvrez notre société Contactez-nous

e-Xpert Solutions Genève

109, chemin du Pont-Du-Centenaire
CH-1228 Plan-les-Ouates / Genève
SUISSE

Tel. : +41 22 727 05 55 Fax : +41 22 727 05 50

e-Xpert Solutions Lausanne

Avenue de Gratta-Paille 20
CH-1018 Lausanne
SUISSE

Tel. : +41 21 802 26 78 Fax : +41 22 727 05 50
Contactez notre support : +41 22 727 05 56
Envoyez votre message

Swiss Security Hackademy

Suivez notre blog bilingue en sécurité informatique !

Retour aux articles

Architecture de notre portail d’application


Auteur : Gilliek

Date de publication : 29 novembre 2017 - Dernière mise à jour : 6 juillet 2018


Dans cet article, nous allons traiter de l’architecture de notre portail d’applications, et plus particulièrement de la partie authentification. Nous allons nous intéresser à tout ce qui a trait à la gestion de l’identité et à l’autorisation. Avant de rentrer dans le vif du sujet, attardons nous un peu sur comment sont organisés notre portail et nos applications. Tout d’abord, nous fonctionnons avec une API REST. Chaque application est en réalité une simple API qui est exposée et l’application web n’est qu’un client de cette API, au même titre que le serait une application mobile Android. Nous avons donc deux parties complètement distinctes et détachées pour chaque application, le backend (API) et le frontend (web app). Afin d’unifier chacun de nos services, nous avons besoin d’un pilier central qui soit capable de gérer la partie identité de nos utilisateurs. Cette application, nommée “Accounts”, se charge de toute la partie authentification, authorization et gestion d’identité de notre portail. Le schema ci-dessous illustre notre portail et ses applications:

Architecture de notre portail d’application

Afin de gérer l’accès à nos API, nous avons choisi d’utiliser le protocol OAuth 2.0 et pour la couche identité nous utilisons le protocol OpenID Connect 1.0, basé sur OAuth. Le schema ci-dessous montre de manière abstraite l’idée derrière le protocol OAuth 2.0, c’est-à-dire comment fonctionnent et intéragissent les composants entre eux:

Architecture de notre portail d’application

source:digitalocean

Détaillons chaque composant impliqués :

  • Application (Client): Il s’agit de l’application qui souhaite accéder à nos API. Dans notre cas, il s’agit typiquement des nos Web Apps.
  • Resource Owner: C’est l’utilisateur qui va donner son consentement au client pour qu’il puisse accéder à ses informations et aux resources auquelles il a accès. Comme il s’agit dans notre cas d’autoriser notre propre application à accéder à nos propres API, le consentement est implicite. Celui-ci est seulement demandé explicitement lorsqu’il s’agit d’une application tierce qui souhaite y accéder.
  • Authorization Server: C’est le serveur qui est en charge de délivrer les jetons d’accès (Access Tokens), et dans le cas d’OpenID Connect de délivrer également les jetons d’identité (ID Tokens). Chez nous ce rôle est assumé par notre serveur OpenID Connect, implémenté dans Accounts.
  • Resource Server: Il s’agit du serveur qui détient la resource à laquelle le client souhaite accéder. Il s’agit dans notre cas de nos divers serveurs d’API (Accounts, Insight, SSLCert, Esas, …). C’est le jeton émis par l’Authorization Server qui va leur permettre de connaître le scope auquel le client à le droit d’accéder.

Pour nos Access Tokens et nos ID Tokens nous utilisons JWT (JSON Web Tokens). Un tel jeton est composé de trois parties: l’en-tête (Header), les données (Payload) et d’une signature cryptographique. Les deux premières parties sont au format JSON. Voici un exemple de jeton JWT:

Architecture de notre portail d’application

On retrouve dans la partie Header les meta-informations concernant le jeton. Typiquement le type, qui sera “JWT”, ainsi que l’algorithme utilisé pour la signature: HS256 (HMAC SHA 256), signature avec une clé symétrique, ou RS256 (RSA et SHA 256), signature asymétrique. Dans la partie Payload, on a les informations du token. On y retrouve un certain nombre de champs prédéfinis par le standard JWT, tels que l’issuer (iss), la date d’expiration (exp) ou encore l’audience à qui le jeton est destiné (aud). Il est également possible d’ajouter des champs librement (name, admin, etc.). Puis, dans la partie Signature, on a la signature du jeton qui est calculée à partir de la représentation en base 64 du Header et du Payload séparé par un point (“.”). Celle-ci permet entre autre de garantir que le jeton n’a pas été alteré (intégrité). Finalement, chacune des parties est encodée en base 64 et séparée par un point “.”. Le jeton présenté dans l’exemple ci-dessus, donnerait donc:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlLXhwZXJ0c29sdXRpb25zLmNvbSIsImV4cCI6MjgwNzgxOT M0MCwibmFtZSI6IktldmluIEdpbGxpZXJvbiIsImFkbWluIjp0cnVlfQ.AvWg0VtLMadrJsn3p8D7lLgbu9oZLjV01OW4i qOWapY

Revenons sur Oauth et OpenID Connect et sur leurs jetons. L’Access Token contient un champ scope qui définit la portée du jeton, c’est-à-dire ce à quoi l’utilisateur aura accès. Il est transmis à chaque requête à l’API, afin que cette dernière puisse décider si oui ou non elle autorise l’accès à la ressource. Quant à l’ID Token, il est contient un certain nombre d’information sur l’utilisateur. Il va permet à l’application de personnaliser l’interface en fonction de l’utilisateur (nom d’utilisateur, adresse email, administrateur ou simple utilisateur, rôles, etc.). L’utilisation de ces protocoles nous permet d’avoir de manière sûre et efficace l’accès à nos APIs, le tout de manière standardisée. Cela offre également la possibilité à des applications tierces, des scripts ou applications mobiles d’exploiter nos API.

// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
type ServiceAccountTokenGetter interface {
GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error)
GetPod(namespace, name string) (*v1.Pod, error)
GetSecret(namespace, name string) (*v1.Secret, error)
}

type TokenGenerator interface {
// GenerateToken generates a token which will identify the given
// ServiceAccount. privateClaims is an interface that will be
// serialized into the JWT payload JSON encoding at the root level of
// the payload object. Public claims take precedent over private
// claims i.e. if both claims and privateClaims have an "exp" field,
// the value in claims will be used.
GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error)
}