Dans ce Post, nous mettons en place OpenID Connect (OIDC) dans une application Web. OIDC est un protocole d’autorisation basé sur le protocole OAuth 2.

  • OAuth 2 permet d’autoriser une application (Client) à utiliser l’API d’une autre application (Resource Server) pour le compte d’un utilisateur (Resource Owner).
  • En plus des mécanismes OAuth 2, OIDC permet à un client, de demander des informations sur l’utilisateur “connecté” (son adresse, ses droits …).

OIDC implémente plusieurs processus d’autorisation; ces processus sont appelés des “grant” (comme octroyer le droit d’utiliser).

Application Cible

L’application à sécuriser est composé d’un Front (implémenté en React) et d’un Back (implémenté en Spring/Java). Les deux applications sont regroupées en une seule application par la mise en place d’un Frontal NGinx.

L’autorisation OIDC est réalisée via un serveur Keycloak.

  • Le “grant” mis en place dans ce Post est le grant “authorization code”.

Les codes sources des deux applications sont disponibles sur Github :

Config Keycloak

Au niveau du Keycloak, nous :

  • Créons un REALM (pour structurer notre gestion des applications)
  • Créons et Configurons une application cliente (pour que Keycloak gère via OIDC la délégation d’autorisation a cette application)

Créer un Realm

  • Créer un Realm dans Keycloak

Jenkins Job

  • Saisir le nom du Realm

Realm name

Créer un Client

  • Cliquer sur Clients dans la barre de menu à gauche

Realm name

  • Cliquer sur le bouton Create

Configurer le Client

  • Cliquer sur l’onglet Settings du Client

Config Client

  • Configurer le Client

    • Définir le Client ID (exemple : book-api)
    • Définir le protocole (openid-connect)
    • Définir le type d’accès (confidential)
    • Activer le Flow Standard (qui correspond au grant authorization code)
    • Préciser la liste des URL de redirection valide

Config BackEnd

Au niveau du BackEnd, nous :

  • Intégrons les dépendances Spring/Oauth au Projet
  • Configurons l’authentification dans l’application (mise en place d’une configuration Spring et d’un paramétrage des services Keycloak)
  • Mettons en place un service de Login utilisé par le Front

Dépendances “Spring OAuth2”

Liste des dépendances à ajouter au pom.xml

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>

Configuration Spring

Exemple de configuration OIDC pour une application SpringBoot.

@Configuration
@EnableResourceServer (1)
@EnableOAuth2Client (2)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OAuth2AuthenticationConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatcher(resources()).authorizeRequests()
                .anyRequest().authenticated(); (3)
    }
}
  1. Configuration d’un serveur de ressources OAuth dans l’application
  2. Configuration d’un client OAuth (pour la connexion au Keycloak)
  3. Toutes les requêtes reçues par le BackEnd doivent être authentifiées

Configuration du Serveur OIDC

Exemple de configuration OIDC (à intégrer dans un fichier de propriétés Spring).

Ce fichier (au format yaml) spécifie notamment les URLs des services utilisés lors de l’authentification.

security:
  oauth2:
    resource:
      user-info-uri: https://<<KEYCLOAK.DOMAIN>>/auth/realms/<<REALM>>/protocol/openid-connect/userinfo (1)
      token-info-uri: https://<<KEYCLOAK.DOMAIN>>/auth/realms/<<REALM>>/protocol/openid-connect/token/introspect (2)
      prefer-token-info: false
    client:
      client-id: books-api (3)
      access-token-uri: https://<<KEYCLOAK.DOMAIN>>/auth/realms/<<REALM>>/protocol/openid-connect/token (4)
      user-authorization-uri: https://<<KEYCLOAK.DOMAIN>>/auth/realms/<<REALM>>/protocol/openid-connect/auth (5)
      scope: openid
      client-secret: "<<CLIENT-SECRET>>" (6)
      grant-type: authorization_code
  1. user-info-uri: API Info Utilisateur
  2. token-info-uri: API introspection des tokens (vérification de validité par exemple)
  3. client-id: ID du client
  4. access-token-uri: API obtention d’un code temporaire
  5. user-authorization-uri: API transformation code temporaire en access token
  6. client-secret: Secret utilisé par le Resource Server pour communiquer avec Keycloak

Service de Login

Exemple de service de Login.

@Controller
public class LoginController {

    @RequestMapping(value = "/app-login", produces = "application/html")
    public String login() {

        return "redirect:/";
    }
}

Config FrontEnd

Au niveau du FrontEnd, nous :

  • Intégrons un lien vers le service de Login

Connexion

La connexion est réalisée par mise en place dans le Front d’un lien vers un service du BackEnd (par exemple /api/app-login).

Quand l’utilisateur clique sur ce lien (sécurisé comme tous les services du BackEnd), il est redirigé vers Keycloak pour saisir son login/password.

Après authentification, l’utilisateur est redirigé vers l’application (cf. “redirect:/” dans LoginController).

Gestion des Erreurs

Nous utilisons le framework axios pour réaliser les requêtes HTTP.

axios permet de centraliser la gestion des erreurs par la mise en place d’un intercepteur. Dans le code ci-dessous nous catchons les appels HTTP en erreur, liés à des problèmes de connexion, pour “demander” à l’utilisateur de se connecter.

axios.interceptors.response.use(response => {
    return response;
}, error => {

    // Gestion de l'erreur CORS (appel du service du Login qui redirige vers le Keycloak qui n'est pas sur le même domaine)
    if (typeof error.response === 'undefined') {
        toast.error("Unknown Error !!! Try to Reconnect !");
    }

    if (error.response.status === 401) {
        toast.info("Connect !!!");
    }
    return Promise.reject(error)
});

Config NGinx

Exemple de configuration NGinx pour l’application (pour structurer le Front et le Back dans une unique application Web).

server {

    # simple reverse-proxy
    listen 80;
    server_name localhost.softeam.fr;

    # Reverse Proxy vers le Back
    location /api {
        proxy_set_header Host $http_host;
        proxy_pass http://localhost:8080/api;
    }

    # Reverse Proxy vers le Front
    location / {

        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header connexion $connection_upgrade;
        proxy_pass http://localhost:3000/;
    }
}