OpenID Connect: autorisation des applications internes du générique au standard

Il y a quelques mois, je mettais en œuvre un serveur OpenID Connect pour contrôler l'accès à des centaines de nos applications internes. De nos propres développements, pratiques à plus petite échelle, nous sommes passés à la norme généralement acceptée. L'accès via un service central simplifie considérablement les opérations monotones, réduit le coût de mise en œuvre des autorisations, vous permet de trouver de nombreuses solutions toutes faites et de ne pas vous casser la tête lors du développement de nouvelles. Dans cet article, je parlerai de cette transition et des bosses que nous avons réussi à combler.

intro

Il était une fois ... Comment tout a commencé


Il y a quelques années, alors qu'il y avait trop d'applications internes de contrôle manuel, nous avons écrit une application de contrôle d'accès au sein de l'entreprise. Il s'agissait d'une simple application Rails qui se connectait à une base de données contenant des informations sur les employés, où l'accès à diverses fonctions était configuré. Ensuite, nous avons soulevé le premier SSO, qui était basé sur la vérification des jetons du client et du serveur d'autorisation, le jeton a été transmis sous forme cryptée avec plusieurs paramètres et vérifié sur le serveur d'autorisation. Ce n'était pas l'option la plus pratique, car sur chaque application interne, il était nécessaire de décrire une couche considérable de logique, et la base des employés était complètement synchronisée avec le serveur d'autorisation.

Après un certain temps, nous avons décidé de simplifier la tâche d'autorisation centralisée. SSO transféré à l'équilibreur. En utilisant OpenResty sur Lua, ils ont ajouté un modèle qui vérifiait les jetons, savait dans quelle application se trouvait la demande et pouvait vérifier s'il y avait accès. Cette approche a grandement simplifié la tâche de contrôle d'accès des applications internes - dans le code de chaque application, il n'était plus nécessaire de décrire une logique supplémentaire. En conséquence, nous avons fermé le trafic en externe et l'application elle-même ne savait rien de l'autorisation.

Cependant, l'un des problèmes n'est toujours pas résolu. Qu'en est-il des applications qui ont besoin d'informations sur les employés? Vous pouvez écrire une API pour le service d'autorisation, mais vous devrez alors ajouter une logique supplémentaire pour chacune de ces applications. De plus, nous voulions nous débarrasser de la dépendance à l'égard de l'une de nos applications auto-écrites, davantage orientée vers la traduction en OpenSource, sur notre serveur d'autorisation interne. Nous parlerons de lui une autre fois. La solution à ces deux problèmes était OAuth.

Aux normes généralement acceptées


OAuth est une norme d'autorisation compréhensible et généralement acceptée, mais comme sa fonctionnalité n'est pas suffisante, OpenID Connect (OIDC) a immédiatement commencé à être envisagé. OIDC lui-même est la troisième implémentation d'une norme d'authentification ouverte qui s'est répandue dans le complément via le protocole OAuth 2.0 (protocole d'autorisation ouvert). Cette solution résout le problème du manque de données sur l'utilisateur final et permet également de changer de fournisseur d'autorisation.

Cependant, nous n'avons pas choisi de fournisseur spécifique et avons décidé d'ajouter l'intégration avec OIDC pour notre serveur d'autorisation existant. En faveur d'une telle solution, OIDC est très flexible en termes d'autorisation de l'utilisateur final. Ainsi, il était possible d'implémenter la prise en charge OIDC sur votre serveur d'autorisation actuel.

image

Notre façon d'implémenter notre propre serveur OIDC


1) Ils ont apporté les données sous la forme souhaitée


Pour intégrer OIDC, vous devez apporter les données utilisateur actuelles de manière compréhensible à la norme. Dans OIDC, cela s'appelle les revendications. Les marques sont essentiellement des champs de fin dans la base de données des utilisateurs (nom, email, téléphone, etc.). Il existe une liste standard de marques et tout ce qui n'est pas inclus dans cette liste est considéré comme personnalisé. Par conséquent, le premier point auquel vous devez faire attention si vous souhaitez choisir un fournisseur OIDC existant est la possibilité de personnaliser facilement de nouvelles marques.

Le groupe de marques est combiné dans le prochain sous-ensemble - Scope. Lors de l'autorisation, l'accès n'est pas demandé à des marques spécifiques, mais à des étendues, même si certaines des caractéristiques de l'étendue ne sont pas nécessaires.

2) mis en œuvre les subventions nécessaires


La prochaine partie de l'intégration OIDC est la sélection et la mise en œuvre des types d'autorisation, les soi-disant subventions. L'autre scénario d'interaction de l'application sélectionnée avec le serveur d'autorisation dépendra de la subvention sélectionnée. Un schéma approximatif pour choisir la bonne subvention est présenté dans la figure ci-dessous.

image

Pour notre première demande, nous avons utilisé la subvention la plus courante - le code d'autorisation. Sa différence avec les autres est qu'elle est en trois étapes, c'est-à-dire passe une vérification supplémentaire. Tout d'abord, l'utilisateur fait une demande d'autorisation, reçoit un jeton - Code d'autorisation, puis avec ce jeton, comme avec un billet de voyage, demande un jeton d'accès. Toute l'interaction principale de ce scénario d'autorisation est basée sur des redirections entre l'application et le serveur d'autorisation. En savoir plus sur cette subvention ici .

OAuth adhère au concept selon lequel les jetons d'accès reçus après l'autorisation doivent être temporaires et changer de préférence en moyenne toutes les 10 minutes. L'octroi du code d'autorisation est un contrôle en trois étapes par le biais de redirections; effectuer cette étape toutes les 10 minutes n'est, franchement, pas une expérience agréable pour les yeux. Pour résoudre ce problème, il existe une autre subvention - Refresh Token, que nous avons également déployée. Ici, tout est plus simple. Pendant le test, à partir d'une autre autorisation, en plus du jeton d'accès principal, un autre est émis - le jeton d'actualisation, qui ne peut être utilisé qu'une seule fois et sa durée de vie, en règle générale, est considérablement plus longue. Avec ce jeton d'actualisation, lorsque le TTL (Time to Live) du jeton d'accès principal se termine, une demande de nouveau jeton d'accès arrivera au point de terminaison d'une autre autorisation. Le jeton d'actualisation utilisé est immédiatement réinitialisé.Une telle vérification est en deux étapes et peut être effectuée en arrière-plan, de manière invisible pour l'utilisateur.

3) Formats de sortie de données utilisateur personnalisés


Une fois les subventions sélectionnées mises en œuvre, l'autorisation fonctionne, il convient de mentionner la réception de données sur l'utilisateur final. OIDC dispose d'un point de terminaison distinct pour cela, sur lequel vous pouvez demander des données utilisateur avec votre jeton d'accès actuel et le cas échéant. Et si les données utilisateur ne changent pas si souvent et que vous devez répéter plusieurs fois le courant, vous pouvez prendre une décision comme les jetons JWT. Ces jetons sont également pris en charge par la norme. Le jeton JWT lui-même se compose de trois parties: en-tête (informations sur le jeton), charge utile (toutes les données nécessaires) et signature (signature, le jeton est signé par le serveur et vous pouvez vérifier la source de sa signature à l'avenir).

Dans une implémentation OIDC, un jeton JWT est appelé id_token. Il peut être demandé avec un jeton d'accès régulier, et il ne reste plus qu'à vérifier la signature. Le serveur d'autorisation a un point de terminaison distinct pour cela avec un tas de clés publiques au format JWK . Et en parlant de cela, il convient de mentionner qu'il existe un autre point de terminaison qui, basé sur la norme RFC5785, reflète la configuration actuelle du serveur OIDC. Il contient toutes les adresses des points de terminaison (y compris l'adresse du trousseau public utilisé pour la signature), les marques et les étendues prises en charge, les algorithmes de chiffrement utilisés, les subventions prises en charge, etc.

Par exemple sur Google:
{
 "issuer": "https://accounts.google.com",
 "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
 "device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
 "token_endpoint": "https://oauth2.googleapis.com/token",
 "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
 "revocation_endpoint": "https://oauth2.googleapis.com/revoke",
 "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
 "response_types_supported": [
  "code",
  "token",
  "id_token",
  "code token",
  "code id_token",
  "token id_token",
  "code token id_token",
  "none"
 ],
 "subject_types_supported": [
  "public"
 ],
 "id_token_signing_alg_values_supported": [
  "RS256"
 ],
 "scopes_supported": [
  "openid",
  "email",
  "profile"
 ],
 "token_endpoint_auth_methods_supported": [
  "client_secret_post",
  "client_secret_basic"
 ],
 "claims_supported": [
  "aud",
  "email",
  "email_verified",
  "exp",
  "family_name",
  "given_name",
  "iat",
  "iss",
  "locale",
  "name",
  "picture",
  "sub"
 ],
 "code_challenge_methods_supported": [
  "plain",
  "S256"
 ],
 "grant_types_supported": [
  "authorization_code",
  "refresh_token",
  "urn:ietf:params:oauth:grant-type:device_code",
  "urn:ietf:params:oauth:grant-type:jwt-bearer"
 ]
}


Ainsi, en utilisant id_token, vous pouvez transférer toutes les caractéristiques nécessaires à la charge utile du jeton et ne pas contacter le serveur d'autorisation à chaque fois pour demander des informations sur l'utilisateur. L'inconvénient de cette approche est que la modification des données utilisateur à partir du serveur ne vient pas immédiatement, mais avec un nouveau jeton d'accès.

Résultats de la mise en œuvre


Ainsi, après avoir implémenté notre propre serveur OIDC et avoir établi des connexions avec celui-ci côté application, nous avons résolu le problème de transmission des informations utilisateur.
OIDC étant un standard ouvert, nous avons la possibilité de choisir un fournisseur ou une implémentation de serveur existant. Nous avons essayé Keycloak, qui s'est avéré très pratique à configurer, après avoir installé et modifié les configurations de connexion côté application, il est prêt à fonctionner. Côté application, il ne reste plus qu'à modifier la configuration de la connexion.

En parlant de solutions existantes


Dans le cadre de notre organisation, en tant que premier serveur OIDC, nous avons mis en place notre implémentation, qui a été complétée au besoin. Après un examen détaillé d'autres solutions toutes faites, nous pouvons dire que c'est un point discutable. Les inquiétudes des fournisseurs concernant le manque de fonctionnalités nécessaires ont servi de solution à la mise en œuvre de leur serveur, ainsi que la présence d'un ancien système dans lequel il y avait diverses autorisations personnalisées pour certains services et beaucoup de données étaient stockées sur les employés. Cependant, dans les implémentations prêtes à l'emploi, l'intégration est pratique. Par exemple, Keycloak a son propre système de gestion des utilisateurs et les données y sont stockées directement, et il ne sera pas difficile d'y dépasser ses utilisateurs. Pour cela, Keycloak dispose d'une API qui vous permettra de mettre en œuvre intégralement toutes les étapes nécessaires au transfert.

Un autre exemple de mise en œuvre certifiée et intéressante à mon avis est Ory Hydra. Il est intéressant en ce qu'il se compose de différents composants. Pour l'intégration, vous devrez lier votre service de gestion des utilisateurs à leur service d'autorisation et étendre si nécessaire.

Keycloak et Ory Hydra ne sont pas les seules solutions clé en main. Il est préférable de sélectionner une implémentation OpenID Foundation certifiée. En règle générale, ces solutions ont un badge de certification OpenID.

Certification Openid


N'oubliez pas non plus les fournisseurs payants existants si vous ne souhaitez pas conserver votre serveur OIDC. Il existe à ce jour de nombreuses bonnes options.

Et après


Dans un avenir proche, nous allons fermer le trafic vers les services internes d'une autre manière. Nous prévoyons de transférer notre SSO actuel sur l'équilibreur à l'aide d'OpenResty vers un proxy basé sur OAuth. Il existe également de nombreuses solutions prêtes à l'emploi, par exemple:
github.com/bitly/oauth2_proxy
github.com/ory/oathkeeper
github.com/keycloak/keycloak-gatekeeper

Matériaux additionnels


jwt.io - bon service pour vérifier les jetons JWT
openid.net/developers/certified - liste des implémentations OIDC certifiées

All Articles