Comment configurer une authentification CAS par IP ?

Clement Dedenis   ven 17/01/2020 - 11:50  219 vues

Comment configurer une authentification CAS par IP

Pour configurer une authentification CAS par IP voici les étapes à suivre :

Nous partons du principe que vous avez suivi le tutoriel “Comment configurer l’authentification CAS”.
Les dossiers dans notre exemple se situent aux emplacements suivants :

  • Pour le CAS    ->   cas60X
  • Pour Tomcat   ->   /opt/tomcat9.0.14

Nous pouvons accéder à Tomcat à partir du navigateur :

 

Deux situations possibles :

  1. Nous utilisons la connexion au CAS depuis une seule IP privée. Le module d’authentification par IP nous permet d’utiliser une seule IP.
  2. Nous utilisons la connexion au CAS depuis plusieurs IP privées. Nous allons faire du code sur mesure pour avoir la possibilité de s’authentifier depuis plusieurs IP.


Première situation : connexion au CAS depuis une seule IP privée.


Pour démarrer la configuration du CAS par IP nous allons commencer par ajouter les dépendances dans le fichier “build.gradle”. Nous ajouterons la ligne suivante dans les dépendances du CAS :

compile "org.apereo.cas:cas-server-support-generic-remote-webflow:${casServerVersion}"

Ensuite nous allons ajouter une ligne de configuration dans le fichier etc/cas/config/cas.properties.

cas.authn.remoteAddress.ipAddressRange=127.0.0.1/255.0.0.0

Pour connaître l’IP  ->  commande “ifconfig” dans le terminal.

On va faire une petite modification au login-webflow du CAS pour finir la configuration du service cas-server-support-generic-remote-webflow.
Pour cela nous allons prendre le fichier “login-webflow.xml” de la dernière version du CAS dans Tomcat.

cd cas60X/src/main
mkdir -p resources/webflow/login
sudo su
cp /opt/tomcat9.0.14/webapps/cas/WEB-INF/classes/webflow/login/login-webflow.xml resources/webflow/login/

Ouvrez le fichier login-webflow.xml que vous venez de copier.  Nous allons modifier une action et en ajouter une autre :

  • Modifier la transition de la première action (id=”initializeLoginForm”) en remplaçant “viewLoginForm” par “startAuthenticate”
  • Créer l’action “startAuthenticate”

Voici le fichier que nous obtenons :

login-webflow.xml :

<action-state id="initializeLoginForm">
    <evaluate expression="initializeLoginAction" />
    <transition on="success" to="startAuthenticate"/>
</action-state>

<action-state id="startAuthenticate">
    <evaluate expression="remoteAddressCheck"/>
    <transition on="success" to="createTicketGrantingTicket"/>
    <transition on="error" to="viewLoginForm"/>
</action-state>

<view-state id="viewLoginForm" view="casLoginView" model="credential">
…

Une fois cette étape réalisée c’est le moment de compiler, de mettre la nouvelle version du CAS dans Tomcat et de faire un test. Si nous allons sur http://127.0.0.1:8334/cas nous serons connecté directement.

 

Deuxième situation : connexion au CAS depuis plusieurs IP privées.


Afin de continuer avec la connexion du CAS par IP nous allons ajouter d’autres dépendances dans le fichier build.gradle.
Dans la partie “buidscript”, catégorie “dependencies” nous ajouterons la ligne :

classpath "io.franzbecker:gradle-lombok:1.14"

Après la ligne “apply plugin: "com.google.cloud.tools.jib"” il faut ajouter :

apply plugin: "io.franzbecker.gradle-lombok"
lombok {
    version = "1.18.4"
}

Sous le commentaire “// Other CAS dependencies/modules may be listed here...” nous devons ajouter la ligne :

compile "org.apereo.cas:cas-server-core-authentication-api:${casServerVersion}"

Ensuite, nous allons supprimer la configuration définie dans la première partie dans le fichier etc/cas/config/cas.properties

#cas.authn.remoteAddress.ipAddressRange=127.0.0.1/255.0.0.0

Puis faire une petite modification au login-webflow du CAS

cd cas60X/src/main

Après avoir ouvert le fichier login-webflow.xml (dans le dossier “resources/webflow/login/”) :

  • Dans l’action “initializeLoginForm” nous changeons la transition“startAuthenticate” par “viewLoginForm”
  • Dans la view “viewLoginForm”, nous ajoutons une seconde transition
  • Nous allons placer l’action “startAuthenticate” sous la view
  • Modifier la transition d’erreur dans l’action “startAuthenticate”

A la fin, nous aurons le fichier suivant :

login-webflow.xml :

<transition on="success" to="viewLoginForm"/>
</action-state>

<view-state id="viewLoginForm" view="casLoginView" model="credential">
    <binder>
        <binding property="username" required="true"/>
        <binding property="password" required="true"/>
        <binding property="source" required="true" />
    </binder>
    <on-render>
         <evaluate expression="renderLoginFormAction" />
    </on-render>
    <transition on="submit" bind="true" validate="true" to="realSubmit" history="invalidate"/>
    <transition on="submitRemote" bind="true" validate="true" to="startAuthenticate" history="invalidate"/>
</view-state>

<action-state id="startAuthenticate">
    <evaluate expression="remoteAddressCheck"/>
    <transition on="success" to="createTicketGrantingTicket"/>
    <transition on="error" to="initializeLoginForm"/>
</action-state>

<action-state id="realSubmit">
        …
        

Puis, nous devons faire une petite modification au loginform du CAS. Pour cela, nous allons prendre le fichier loginform.html de la dernière version de CAS dans Tomcat.

mkdir -p resources/templates/fragments
sudo su
cp /opt/tomcat9.0.14/webapps/cas/WEB-INF/classes/templates/fragments/loginform.html resources/templates/fragments/

Il faut ouvrir le fichier “loginform.html” que nous avons copié depuis Tomcat puis ajouter un formulaire pour lancer la connexion par IP en cliquant sur un lien. Finalement nous aurons le code suivant.

loginform.html :

<form method="post" id="fm1" th:object="${credential}" action="login">

    …

</form>

<form method="post" id="fmLoginIp" class="mt-5">
    <input type="hidden" name="execution" th:value="${flowExecutionKey}"/>
    <input type="hidden" name="_eventId" value="submitRemote"/>
    <a class="d-block text-center"
        href="javascript:void(0)"
        onclick="$('#fmLoginIp').submit();"
        />Je me connecte via mon ip<a>
</form>

<form th:if="${passwordManagementEnabled}" method="post" id="passwordManagementForm">

    …

Ensuite, nous continuons les modifications au service d’authentification par IP en créant quelques fichiers java.
Nous allons créer un dossier remote/ :

mkdir -p java/com/server/cas/demo/adaptors/generic/remote

Et définir une classe Handler dans ce dossier :

touch TestRemoteAddressAuthenticationHandler.java

Nous devons modifier le fonctionnement du gestionnaire d'authentification pour avoir la possibilité de nous connecter depuis plusieurs adresses IP. Dans la classe TestRemoteAddressAuthenticationHandler.java nous allons réécrire les fonctions qui définissent si une IP est valide (“configureIpNetworkRange” et “containsAddress”) pour déclarer plusieurs adresses IP (“configureIpNetworkRangeMiltiple”) et finalement faire l’authentification par IP (“authenticate”).

TestRemoteAddressAuthenticationHandler.java :

package com.server.cas.demo.adaptors.generic.remote;

import org.apereo.cas.adaptors.generic.remote.RemoteAddressAuthenticationHandler;
import org.apereo.cas.adaptors.generic.remote.RemoteAddressCredential;
import org.apereo.cas.authentication.AbstractAuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.DefaultAuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import com.google.common.base.Splitter;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import javax.security.auth.login.FailedLoginException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Checks if the remote address is in the range of allowed addresses.
 */

@Slf4j
@Setter
@Getter
public class TestRemoteAddressAuthenticationHandler extends RemoteAddressAuthenticationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(TestRemoteAddressAuthenticationHandler.class);

    private static final int HEX_RIGHT_SHIFT_COEFFICIENT = 0xff;

    /**
     * The network netmask.
     */

    private List <InetAddress> inetNetmasks = new ArrayList();

    /**
     * The network base address.
     */

    private List <InetAddress> inetNetworkRanges = new ArrayList();

    public TestRemoteAddressAuthenticationHandler(final String name, final ServicesManager servicesManager,
                                              final PrincipalFactory principalFactory,
                                              final Integer order) {
        super(name, servicesManager, principalFactory, order);
    }

    /**
     * Checks if a subnet contains a specific IP address.
     *
     * @param network The network address.
     * @param netmask The subnet mask.
     * @param ip      The IP address to check.
     * @return A boolean value.
     */

    private static boolean containsAddress(final InetAddress network, final InetAddress netmask, final InetAddress ip) {

        LOGGER.debug("Checking IP address: [{}] in [{}] by [{}]", ip, network, netmask);
        val networkBytes = network.getAddress();
        val netmaskBytes = netmask.getAddress();
        val ipBytes = ip.getAddress();
        /* check IPv4/v6-compatibility or parameters: */
        if (networkBytes.length != netmaskBytes.length || netmaskBytes.length != ipBytes.length) {
            LOGGER.debug("Network address [{}], subnet mask [{}] and/or host address [{}]" + " have different sizes! (return false ...)", network, netmask, ip);
            return false;
        }
        /* Check if the masked network and ip addresses match: */

        for (var i = 0; i < netmaskBytes.length; i++) {
            val mask = netmaskBytes[i] & HEX_RIGHT_SHIFT_COEFFICIENT;
            if ((networkBytes[i] & mask) != (ipBytes[i] & mask)) {
                LOGGER.debug("[{}] is not in [{}]/[{}]", ip, network, netmask);
                return false;
            }
        }
        LOGGER.debug("[{}] is in [{}]/[{}]", ip, network, netmask);
        return true;
    }

    @Override
    public AuthenticationHandlerExecutionResult authenticate(final Credential credential) throws GeneralSecurityException {
        val c = (RemoteAddressCredential) credential;
        if (this.inetNetmasks != null && this.inetNetworkRanges != null) {
            try {
                val inetAddress = InetAddress.getByName(c.getRemoteAddress().trim());
                for (int i = 0; i < inetNetworkRanges.size(); i++) {
                    if (containsAddress(this.inetNetworkRanges.get(i), this.inetNetmasks.get(i), inetAddress)) {
                        return new DefaultAuthenticationHandlerExecutionResult(this, c, this.principalFactory.createPrincipal(c.getId()));
                    }
                }
            } catch (final UnknownHostException e) {
                LOGGER.debug("Unknown host [{}]", c.getRemoteAddress());
            }
        }
        throw new FailedLoginException(c.getRemoteAddress() + " not in allowed range.");
    }

    /**
     * Sets ip network range.
     *
     * @param ipAddressRange the IP address range that should be allowed trusted logins
     */

    @Override
    public void configureIpNetworkRange(final String ipAddressRange) {
        if (StringUtils.isNotBlank(ipAddressRange)) {
            val splitAddress = Splitter.on("/").splitToList(ipAddressRange);
            if (splitAddress.size() == 2) {
                val network = splitAddress.get(0).trim();
                val netmask = splitAddress.get(1).trim();
                try {
                    this.inetNetworkRanges.add(InetAddress.getByName(network));
                    LOGGER.debug("InetAddress network: [{}]", InetAddress.getByName(network).toString());
                } catch (final UnknownHostException e) {
                    LOGGER.error("The network address was not valid: [{}]", e.getMessage());
                }
                try {
                    this.inetNetmasks.add(InetAddress.getByName(netmask));
                    LOGGER.debug("InetAddress netmask: [{}]", InetAddress.getByName(netmask).toString());
                } catch (final UnknownHostException e) {
                    LOGGER.error("The network netmask was not valid: [{}]", e.getMessage());
                }
            }
        }
    }

    /**
     * Liste d'Ip
     */

    public void configureIpNetworkRangeMiltiple() {
        final List <String> ipAddressRangeMiltiple = new ArrayList();
        ipAddressRangeMiltiple.add("127.0.0.1/255.0.0.0");
        ipAddressRangeMiltiple.add("172.0.0.1/255.0.0.0");
        for (int i = 0; i < ipAddressRangeMiltiple.size(); i++) {
            configureIpNetworkRange(ipAddressRangeMiltiple.get(i));
        }
    }
}

Maintenant nous devons créer un dossier qui contiendra tous nos fichiers de configuration.

mkdir -p java/com/server/cas/demo/config

Dans ce dossier nous allons créer la classe TestCasRemoteAuthenticationConfiguration.java.

touch TestCasRemoteAuthenticationConfiguration.java

Dans cette classe nous allons écrire la configuration nécessaire pour valider les modifications faites sur le gestionnaire d’authentification.

TestCasRemoteAuthenticationConfiguration.java :

package com.server.cas.demo.config;

import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.config.CasRemoteAuthenticationConfiguration;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import lombok.val;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.server.cas.demo.adaptors.generic.remote.TestRemoteAddressAuthenticationHandler;

/**
 * This is {@link TestCasRemoteAuthenticationConfiguration}.
 *
 */

@Configuration("testCasRemoteAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class TestCasRemoteAuthenticationConfiguration extends CasRemoteAuthenticationConfiguration {

    @Autowired
    @Qualifier("servicesManager")
    private ObjectProvider<ServicesManager> servicesManager;

    @Autowired
    private CasConfigurationProperties casProperties;

    @Autowired
    @Qualifier("defaultPrincipalResolver")
    private ObjectProvider<PrincipalResolver> defaultPrincipalResolver;

    @Bean
    @RefreshScope
    public AuthenticationHandler testRemoteAddressAuthenticationHandler() {
        val remoteAddress = casProperties.getAuthn().getRemoteAddress();
        val bean = new TestRemoteAddressAuthenticationHandler(remoteAddress.getName(),
            servicesManager.getIfAvailable(),
            remoteAddressPrincipalFactory(),
            remoteAddress.getOrder());
        bean.configureIpNetworkRangeMiltiple();
        return bean;

    }

    @Override
    public AuthenticationEventExecutionPlanConfigurer remoteAddressAuthenticationEventExecutionPlanConfigurer() {
        return plan -> plan.registerAuthenticationHandlerWithPrincipalResolver(testRemoteAddressAuthenticationHandler(), defaultPrincipalResolver.getIfAvailable());
    }
}


Pour finir, nous devons valider les modifications java dans le fichier spring.factories (dossier : resources/META-INF/) et ajouter la ligne suivante :

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.server.cas.demo.config.TestRegisteredServicesConfiguration,\ com.server.cas.demo.config.TestCasRemoteAuthenticationConfiguration

Vous savez dès à présent comment configurer une authentification CAS par IP !

À propos Clément

Clément est le développeur qu’il vous faut ! Il s’occupe des debugs et résout tout autre problème à traiter en un temps record.
Il élabore des solutions innovantes en respectant les bonnes pratiques de programmation pour répondre à toutes vos attentes.

Son autonomie et sa rigueur

Le montage des meubles en kit

Articles liés

Ajouter un commentaire

This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.