Déplacer l’administration de WordPress

Publié le 31 mai 2012 Optimisation technique de WordPress
Lionel Pointet

J’ai effectué une mission de quelques moi au sein d’un grand groupe de presse. Au sein de l’équipe, je me suis occupé de migrer l’ancien backoffice « Web » vers le nouveau : WordPress.

L’architecture technique de ce site est en composée de trois serveurs frontaux (devant s’occuper de l’affichage du site) et d’un serveur de base de données. En réalité, seuls deux des serveurs frontaux sont utilisés pour afficher le site, le troisième ayant été délaissé suite à une incompatibilité de l’application de réplication du cache.

Ainsi, à la fin de cette migration, les deux serveurs frontaux ont dû répondre à la fois au frontoffice du site, mais également au backoffice. Le fait d’avoir un troisième serveur, inexploité, a fait surgir une idée: déplacer cette charge du backoffice sur ce serveur. Les deux serveurs frontaux porteraient alors bien leur nom et l’autre aurait une réelle utilité.

Le principe

Sur le papier, cela reste cohérent et l’architecture n’est pas si complexe puisque les personnes se connectant sur le backoffice travaillent au sein des locaux de mon client: on n’ouvre pas le serveur vers l’extérieur, mais on redirige un sous-domaine (du style « admin.mondomaine.fr« ) vers ce serveur.

Il est même simple de régler certains détails comme le cache, puisqu’on peut toujours demander à ce serveur « back » de se connecter sur un des caches « front ».

Reste alors un problème : WordPress ne connaît qu’une URL pour son admin. C’est www. mondomaine.fr.fr/wp-admin et pas autre chose. Comme vous le savez, WordPress stocke les URL en dur dans la base, et certaines fonctions du coeur ont même la chaîne ‘wp-admin’ codée en dur ! Cela implique que WordPress redirige quiconque essaie d’accéder à son administration vers cette URL.

A première vue, c’est donc pratiquement mission impossible de déplacer l’administration sur un sous-domaine, surtout si on ne veut pas toucher au cœur…

Challenge accepted!

Le VHost

Je ne vais pas expliquer ici ce qu’est un VHost, mais pour ceux que ça intéresse, Wikipédia propose une définition claire et concise.

Une méthode de développement possible avec WordPress est d’installer un serveur HTTP local puis de créer un VHost afin de rediriger l’URL du site de production vers la version locale. Cela permet de ne pas s’embrouiller avec les URLs enregistrées dans la base : ce sont les mêmes.

Chez ce client, c’est un peu différent puisqu’il y a plusieurs environnements à gérer. Cela n’empêche pas la méthode des VHosts en local, puisque cela a l’avantage de permettre une configuration quasi-identique (les URLs se ressemblent, on peut définir des variables d’environnement pour le VHost, l’installation multisite de WordPress accepte le mode « sous-domaines », …).

Ainsi, voici un exemple de VHost pointant vers la partie administration d’une installation de WordPress.

<VirtualHost *:80>
        ServerName admin.mondomaine.fr
        ServerAdmin monmail@monmail.com
        DocumentRoot "C:/wamp/www/mondomaine.fr/wp-admin/"
        ErrorLog "logs/mondomaine.error.log"
        CustomLog "logs/mondomaine.access.log" common
        <Directory "C:/wamp/www/mondomaine.fr/wp-admin/">
            Options FollowSymLinks
            AllowOverride All
            Order deny,allow
            Deny from all
            Allow from all
	</Directory>
</VirtualHost>

Wp-login.php

Le fonctionnement de WordPress pour la phase de connexion est simple : on redirige l’utilisateur vers l’URL « <site_url>/wp-login.php« , où <site_url> est l’adresse du front. Cela nous pose problème puisqu’on veut également que cette phase de connexion soit prise en charge par notre administration.

Ceci nous impose donc de copier le fichier wp-login.php vers le dossier wp-admin/ où est située toute l’administration. Étant donné que c’est un fichier qui se rajoute, il a peu de chances de se faire écraser par une mise à jour de WordPress et c’est tant mieux.

Une fois ce fichier copié, il va falloir un petit peu le modifier. D’une part parce que le bootstrap n’est plus à jour au niveau de l’arborescence, et puis parce que les formulaires pointent toujours vers l’ancien fichier.

Du coup, on va effectuer les modifications suivantes :

  • ligne 12 :
require( dirname(__FILE__) . '/../wp-load.php' );
  • lignes 404, 451, 510 et 630 :
action="<?php echo esc_url( admin_url( 'wp-login.php', 'login_post' ) ); ?>"

On a ainsi un fichier wp-admin/wp-login.php qui charge correctement le cœur de WordPress et qui se rappelle lui-même lors d’un appel de formulaire… Enfin presque.

Effectivement, la fonction admin_url() retourne toujours « mondomaine.fr/wp-admin/« , ce n’est pas vraiment ce qui est voulu.
Et puis de toute façon, WordPress ne connaissant pas cette URL, il ne veut pas afficher notre belle page.

Encore un petit effort.

La configuration

A partir de cette étape, on va mettre en place la configuration nécessaire pour faire croire à WordPress que l’URL de son administration est bien notre sous-domaine.

Tout va se passer dans le fichier wp-config.php. Tout d’abord, il va falloir définir une nouvelle constante afin de stocker cette URL. Appelons-la par exemple ‘ADMIN_SITEURL’ :

define('ADMIN_SITEURL', 'http://admin.mondomaine.fr');

Ensuite, afin que la connexion se passe bien et que WordPress puisse retrouver les données grâce à ses cookies, il faut surcharger 2 constantes définies normalement par le coeur. La première définit le chemin d’accès à l’administration (sans le domaine), donc par défaut ‘/wp-admin‘. La deuxième définit le domaine sur lequel s’appliquent les cookies, soit « mondomaine.fr » par défaut.

define( 'ADMIN_COOKIE_PATH', FALSE );
define( 'COOKIE_DOMAIN', '.mondomaine.fr' );

Ici, on définit donc que les cookies sont partagés entre tous les sous-domaines de « mondomaine.fr » et que le cookie de l’admin sera disponible depuis la racine.

Enfin, toujours dans le fichier wp-config.php, afin d’éviter une petite erreur PHP qui survient après avoir fait tout ça dans le fichier wp-includes/vars.php (à la ligne 28), il convient d’ajouter le bout de code suivant :

if('http://' . $_SERVER['HTTP_HOST'] == ADMIN_SITEURL)
	$_SERVER['PHP_SELF'] = '/wp-admin' . $_SERVER['PHP_SELF'];

Le fait de corriger l’erreur permet également de bien définir la variable $pagenow, variable globale disponible partout dans l’administration, et sur laquelle se basent quelques contrôles et/ou fonctionnalités.

Les filtres

Nous voyons le bout du tunnel désormais ! Toute la préparation a été faite et même les cookies sont bien cuits, il ne nous reste plus qu’à modifier les valeurs que récupère WordPress grâce à quelques fonctions et quelques filtres. Ces filtres pourront être placés dans le thème (le fichier functions.php par exemple), ou dans un plugin dédié.

Première valeur : l’URL de l’administration, bien sûr ! La fonction admin_url() est là pour ça, et elle contient un filtre : « admin_url« .

    function mondomaine_admin_url($admin_url, $path) {
        // Admin-ajax must be accessed from the front (otherwise the AJAX calls won't work because of cross-domain securities)
        // so don't overwrite this URL in case we found 'admin-ajax.php' in the needed URL
        if(is_admin()|| 0 !== strpos($path, 'admin-ajax.php')){
            $admin_url = ADMIN_SITEURL.'/';

            if ( !empty($path) && is_string($path) && strpos($path, '..') === false )
                $admin_url .= ltrim($path, '/');
        }

        return $admin_url;
    }

    add_filter('admin_url', 'mondomaine_admin_url', 10, 2);

On reprend ici le fonctionnement de la véritable fonction admin_url() du coeur, sauf que l’on prend comme URL de base notre sous-domaine.

Petite formalité en plus : on vérifie si l’URL demandée n’est pas celle du fichier « admin-ajax.php« . En effet, ce fichier sert d’agrégateur pour tous les appels AJAX faits au sein de WordPress, que ce soit depuis l’administration ou depuis le frontoffice. Ce qui veut dire que si l’on a installé un plugin qui effectue des appels AJAX depuis le frontoffice, il ira taper sur cette URL.

On pourrait le laisser avec le sous-domaine, sauf que pour des raisons de sécurité, il est interdit de faire des appels AJAX entre deux domaines différents : l’appel va donc échouer… Il serait possible de mettre en place des autorisations spécifiques pour laisser passer ces appels, mais ce n’est pas le but de notre solution ici : on laisse donc ce fichier accessible depuis le front à l’adresse habituelle (mondomaine.fr/wp-admin/admin-ajax.php).

Deuxième valeur : l’URL de la page de connexion. Sur le même modèle que la fonction admin_url()login_url() peut être filtrée.

function mondomaine_login_url($login_url_wp, $redirect) {
        $login_url = admin_url('wp-login.php', 'login');

        if ( !empty($redirect) )
            $login_url = add_query_arg('redirect_to', urlencode($redirect), $login_url);

        if ( FALSE !== strpos($login_url_wp, 'reauth') )
            $login_url = add_query_arg('reauth', '1', $login_url);

        return $login_url;
    }

    add_filter('login_url', 'mondomaine_login_url', 10, 2);

Toujours de la même manière, on reprend le fonctionnement de la fonction du coeur de WordPress, mais en indiquant que notre fichier « wp-login.php » se trouve dans l’administration.

Troisième valeur : l’URL de déconnexion. On commence à être habitués : il existe un filtre « logout_url« .

    function mondomaine_logout_url($logout_url_wp, $redirect) {
        $args = array( 'action' => 'logout' );
        if ( !empty($redirect) ) {
            $args['redirect_to'] = urlencode( $redirect );
        }

        $logout_url = add_query_arg($args, admin_url('wp-login.php', 'login'));
        $logout_url = wp_nonce_url( $logout_url, 'log-out' );

        return $logout_url;
    }

    add_filter('logout_url', 'mondomaine_logout_url', 10, 2);

Encore une fois, on ne change que l’URL du fichier « wp-login.php » par rapport à la fonction du coeur.

La délivrance

Ca y est ?! Et bien oui. L’administration est accessible depuis l’URL « admin.mondomaine.fr« , on peut s’y connecter, y naviguer et tout ça sans avoir perdu les fonctionnalités de base de WordPress.

Pourtant, je ne suis pas sûr à 100% que tous les cas de figure soient pris en compte ici. Je n’ai eu besoin que de ces filtres, mais peut-être y en a-t-il d’autres à ajouter pour compléter la configuration.
De plus, si certains plugins que vous avez installés n’utilisent pas les fonctions dédiées comme admin_url(), il y aura sûrement quelques effets de bord…

Dernier point, et pas des moindres : Internet Explorer.
Lors de la mise en place de mon POC (Proof Of Concept), j’ai été confronté à un bug assez perturbant concernant IE. Lorsque l’on se connectait à l’administration, puis qu’on voulait afficher le frontoffice du site, la page plantait au premier appel de « document.write(‘</style>’);« , et seulement cet appel (les autres « document.write » passaient).

Après plusieurs recherches du côté des cookies ou de l’objet javascript document géré par IE, je suis arrivé à déterminer qu’une portion de code pouvait être retirée pour que ce bug n’apparaisse plus.
Cette portion de code est un ajout de style fait par WordPress directement dans le <head> de la page, nécessaire pour ne pas imprimer la barre d’administration avec le reste de la page :

<style type="text/css" media="print">#wpadminbar { display:none; }</style>

Ce code est ajouté par la fonction wp_admin_bar_header(), appelée lors d’une action WordPress : « wp_head« . Il suffisait donc de supprimer l’appel de cette fonction sur ce hook pour ne plus avoir le bug.

    function mondomaine_remove_admin_bar_header() {
        remove_action('wp_head', 'wp_admin_bar_header');
    }

    add_action('init', 'mondomaine_remove_admin_bar_header', 11);

Afin de ne pas perdre la fonctionnalité qui consiste à cacher la barre d’admin lors d’une impression, il convient tout de même de remettre ce style quelque part, par exemple au sein d’une feuille de style dédiée au print déjà incluse sur le site.

Pour aller plus loin

Je suis preneur de commentaires ou retours d’expérience sur ce système de déplacement de l’administration : est-ce que vous trouvez cela utile ? Allez-vous vous servir de cette astuce ? Avez-vous déjà essayé mais abandonné puisqu’aucune documentation n’existe ? Avez-vous des remarques, des questions sur la mise en place ?
Bref, qu’en pensez-vous ?

Twitter de l’agence : @GLOBALISms

Le site de l’agence : WP Chrono.

Lionel Pointet

Ingénieur et chef de projet Web

18 Commentaires

Stéphane Le 31 mai 2012 à 8h24
Wahh eh beh ! Je n'avais jamais pensé à déplacer l'administration du site et à première vue je n'aurai pas cru que ce serait spécialement difficile.
Sauf que, comme tu dis, "monsite.com/wp-admin" est la seule page reconnue par wordpress donc...
En tout cas merci pour toutes ces explications, c'était très agréable à lire et si je souhaite déplacer mon admin un jour, je saurai vers quel site me tourner :p
Yoann Le 31 mai 2012 à 9h23
Oui vraiment, c'est un bel article qui devrait me servir dans quelques semaines.
D'un point de vue sécurité c'est un véritable plus.
Les développeurs de wordpress devraient prévoir cette possibilité dans une future version !
Willy Bahuaud Le 31 mai 2012 à 9h28
Waw !! Elle à l'air énorme ta technique !
Et le tuto est vraiment clair, je vais essayer ça dès que possible...

Je reste assez sceptique sur le comportement de certains plugins par contre...
DrKokai Le 31 mai 2012 à 9h45
Hello,

J'aurais bientôt 4 sites et j'aimerai avoir qu'un back office pour les 4 et pouvoir les gérer depuis la même place.
Est ce possible?
Fredo Le 31 mai 2012 à 10h08
Tres intéressant ! Mais quid de cette modification lors des prochaines mises à jour de Wordpress ? Devra-t-on tout recommencer ou pas ?
Diije Le 31 mai 2012 à 10h12
Une astuce utile en effet, même si à mon humble avis un htpassword sur wp-login.php et le répertoire wp-admin suffit à repousser pas mal d'attaques.
Soul Le 31 mai 2012 à 12h04
C'est quand même super mal fait, j'aurais pensé pouvoir changer le nom du répertoire sans problèmes mais non.
En espérant qu'un WP_BASE_ADMIN apparaisse un jour...
Lionel Pointet Le 31 mai 2012 à 14h12
@tous : merci pour avoir lu et apparemment apprécié l'article.

@Willy : je suis également très prudent quant à l'utilisation de cette technique avec des plugins, il faut bien vérifier que rien n'est dégradé avant de mettre le tout en production.

@DrKokai : ce n'était pas l'objectif de l'article mais tu peux soit utiliser la fonctionnalité Multisite de WordPress (http://codex.wordpress.org/Create_A_Network), soit utiliser une application tierce comme ManageWP (http://wpmagazine.fr/managewp-gerer-simultanement-plusieurs-sites-wordpress/).

@Fredo : comme expliqué, la seule contrainte par rapport à l'arborescence de base de WordPress est le fichier wp-login.php déplacé. Vu que c'est un fichier ajouté par rapport aux autres, les mises à jour ne le suppriment pas et ne l'écrasent pas non plus. Pour le reste, ce sont des filtres qui peuvent être inclus dans un thème ou un plugin donc aucun risque.

@Diije : le but de la manipulation n'était pas la sécurité, mais la répartition de charge : on voulait que ce soit un seul serveur qui gère toute l'administration et qu'il soit différent de ceux qui gèrent le front. On peut désormais rediriger le sous-domaine vers ce serveur en particulier.
Pour la sécurité, il y a déjà des restricitions par IP sur le dossier wp-admin, donc aucun soucis de ce côté-là ;).
DrKokai Le 31 mai 2012 à 15h35
Hello,

Je passais par là et administration => administration alors j'ai poser un comment!
Merci, pour ta réponse
Screenfeed Le 01 juin 2012 à 2h57
Salut.

Bravo, belle astuce, et le tout sans toucher au core.
Je pense que ça ne posera pas de problème avec la majorité des plugins (du moins, ceux qui sont encore "en activité", c'est à dire mis à jour régulièrement), mais en effet il vaut mieux vérifier à chaque fois.

Bien vu pour l'ajax. Si j'avais tenté une telle manip (ce qui n'est pas vraiment dans mes cordes quand même ^^), il m'aurait fallut un bon moment pour comprendre que l'ajax ne fonctionnait plus à cause du cross-domain (oui, je joue rarement entre plusieurs domaines, donc j'y pense jamais ^^). Et pour les cookies en... j'aurais jamais trouvé la solution je pense haha.

Bref, jolie manip', merci pour le partage :)
Keeg Le 01 juin 2012 à 19h31
Je prends, je crois que ça va servir pas mal d'entre nous sur des projets sensibles.
mickaël andrieu Le 01 juin 2012 à 21h01
Impressionnant :) ça me rappelle tout bas que je suis encore loin de maîtriser Wordpress tant ses possibilités sont exceptionnelles (un moteur de blog, comme me disaient mes profs ^^)
champagne Le 21 juillet 2012 à 13h22
Effectivement, c'est une manipulation très délicate...

Je pense surtout que ce déplacement va accroitre la sécurité et minimiser les risque de piratage par des petit bidouilleur de dimanche, ça les perturbera.

Et merci pour ce petit tuto, je vais déjà tester ça en local avant car je suis pas très bon pour bidouiller les codes.

Merci encore et dès que j'ai déplacé mon back office je vous tient au courant !
JB Le 30 septembre 2013 à 3h00
Pas tres utile et risque de cassez beaucoup de chose dans le fonctionnement meme de wp le plus simple est de sécurisé les page wp login wp admin et autre de cette maniere en passant par le ht access :

[DERCRIPTION DU FONCTIONNEMENT}
- mot-au-choix-login = un mot au choix qui remplacera /wp-login.php
- mot-au-choix-admin = un mot au choix qui remplacera /wp-admin
- mot-au-choix-nouvel_utilisateur = un mot au choix qui remplacera la page d'enregistrement d'un nouvel utilisateur
- CLEF = une suite de lettre et de nombre au choix de préférence assez longue (20 caractère minimum)
URL-DE-VOTRE-SITE = url de votre site


[pastacode lang="bash" provider="manual"]# CACHER LOGIN, REGISTER, ADMIN
# wp-login = mot-au-choix-login
RewriteRule ^mot-au-choix-login/?$ /wp-login.php?CLEF [R,L]
# wp-admin = mot-au-choix-admin
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in_.*$
RewriteRule ^mot-au-choix-admin/?$ /wp-login.php?CLEF&redirect_to=/wp-admin/ [R,L]
#
RewriteRule ^mot-au-choix-admin/?$ /wp-admin/?CLEF [R,L]
# wp-login register = mot-au-choix-nouvel_utilisateur
RewriteRule ^mot-au-choix-nouvel_utilisateur/?$ /wp-login.php?CLEF&action=register [R,L]
#
RewriteCond %{SCRIPT_FILENAME} !^(.*)admin-ajax.php
RewriteCond %{HTTP_REFERER} !^(.*)URL-DE-VOTRE-SITE/wp-admin
RewriteCond %{HTTP_REFERER} !^(.*)URL-DE-VOTRE-SITE/wp-login.php
RewriteCond %{HTTP_REFERER} !^(.*)URL-DE-VOTRE-SITE/mot-au-choix-login
RewriteCond %{HTTP_REFERER} !^(.*)URL-DE-VOTRE-SITE/mot-au-choix-admin
RewriteCond %{HTTP_REFERER} !^(.*)URL-DE-VOTRE-SITE/mot-au-choix-nouvel_utilisateur
RewriteCond %{QUERY_STRING} !^CLEF
RewriteCond %{QUERY_STRING} !^action=logout
RewriteCond %{QUERY_STRING} !^action=rp
RewriteCond %{QUERY_STRING} !^action=register
RewriteCond %{QUERY_STRING} !^action=postpass
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in_.*$
# Si tentative d'entrer sur les ancienne url message : try_again
RewriteRule ^.*wp-admin/?|^.*wp-login.php /try_again [R,L]
#
RewriteCond %{QUERY_STRING} ^loggedout=true
RewriteRule ^.*$ /wp-login.php?CLEF [R,L][/pastacode]


une fois les changements effectué dans votre fichier htaccess il faudra alors vous connecter a l'adresse :
votre-site.com/mot-au-choix-login

Have FUN
yanis Le 01 juillet 2015 à 12h33
Bonjour;

Vous pourriez SVP nous donner plus de précision sur les lignes 404, 451, 510 et 630 à modifier par la ligne : action=""
Selon les versions de WP on est pas à la bonne ligne.

Merci
    Daniel Roch Le 02 juillet 2015 à 9h05
    C'est un vieil article. J'aurais tendance à déconseiller cette méthodologie, et par exemple utiliser plutôt des extensions qui ont le même objectif de sécurité, comme SF Move Login.
yanis Le 02 juillet 2015 à 9h27
Bonjour Daniel:

OK merci de ton conseil mais par contre si tu peux nous donner un peu plus de détails sur les autres solutions ?
En te remerciant.
    Daniel Roch Le 02 juillet 2015 à 10h00
    Cet article visait à déplacer l'administration d'un site pour la protéger.

    Le plugin SF Move Login va permettre de changer les URL pour y accéder, ce qui revient au même (mais qui sera bien plus propre que la méthode présentée dans cet article).

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *