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
- Saisir le nom du Realm
Créer un Client
- Cliquer sur Clients dans la barre de menu à gauche
- Cliquer sur le bouton Create
Configurer le Client
- Cliquer sur l’onglet Settings du 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)
}
}
- Configuration d’un serveur de ressources OAuth dans l’application
- Configuration d’un client OAuth (pour la connexion au Keycloak)
- 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
- user-info-uri: API Info Utilisateur
- token-info-uri: API introspection des tokens (vérification de validité par exemple)
- client-id: ID du client
- access-token-uri: API obtention d’un code temporaire
- user-authorization-uri: API transformation code temporaire en access token
- 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/;
}
}