Image of an arrow

Authentification à Office 365 dans Liferay

Avatar

Jonatan Cloutier

Liferay nous donne la possibilité d’ajouter l’authentification Oauth (OpenId connect). Il est aussi possible de donner accès à un service externe avec une authentification OAuth provenant de Liferay. Mais si vous souhaitez ajouter des fonctionnalités de service externe sans que celui-ci devienne votre authentification principale il faut ajouter cette fonctionnalité indépendamment. Suite à mon article précédent à propos de l’intégration d’office 365 dans une application java web, nous allons regarder comment intégré cette authentification dans un portail Liferay.

Gérer le processus d’authentification

Pour utiliser un service authentifié avec Oauth dans notre portail, nous devons commencer par gérer le processus d’authentification développer dans l’article précédent. Comme Liferay supporte les filtres de servlet, nous allons directement le configurer comme composant OSGI afin qu’il réponde à nos requêtes vers Liferay.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 @Component(
	immediate = true,
	property = {
		"servlet-context-name=",
		"servlet-filter-name=office365-login-filter",
		"url-pattern=/o/o365/login",
	},
	service = Filter.class
 )
 public final class Office365LoginFilter extends BaseFilter {

 

L’élément le plus important est le url-pattern, celui-ci indique au servlet à quelle URL notre filtre doit répondre. En indiquant une URL directe, la seule chose à faire pour lancer le processus est de diriger un utilisateur vers cette URL. Si nous devons spécifier un page cible ou envoyer l’utilisateur après que le processus d’authentification soit complété, il faut ajouter l’URL encodée au paramètre backURL. L’URL finale ressemble donc à /o/o365/login?backURL=%2Fweb%2Fguest%2Fo365Loggedin

Une fois que le filtre reçoit un code d’authentification, un appel à authenticationService.validateIdToken(authentication, code); permet de compléter l’authentification du coter serveur.

Conserver les tokens

Notre utilisateur est authentifié du côté du service externe et nous avons reçu un code d’authentification. Mais si on veut accéder à ces services dans le futur, il faut stocker les informations de connexion, soit l’access et le refresh tokens. C’est ici que l’interface O365Authentication devient pertinente :

1
2
3
4
5
6
7
 public interface O365Authentication {
    Serializable getAccessToken();
    String getRefreshToken();
    Instant getAccessTokenExpireAt();
    void setAccessToken(Serializable accessToken);
    String getCallBackURL();
 }

 

Cette interface permet d’accéder à toutes les données qui doivent être conservées sans se soucier de comment ils seront stockés. Nous pourrions utiliser la même interface pour faire fonctionner la librairie dans un contexte non Liferay en fournissant une implémentation appropriée.

Comme nous souhaitons l’utiliser avec Liferay, nous avons décidé d’implémenter cette classe afin de sauvegarder les informations à court terme ou fréquemment utilisées dans la session HTTP de l’utilisateur. C’est information inclue l’access token et le temps d’expiration du token. Les données qui doivent être conservées à plus long terme sont sauvegardées dans les préférences utilisateur Liferay. Je ne montre le code que pour setAccessToken comme c’est la méthode qui sauvegarde le tout. Les autres méthodes permettent de récupérer les informations à partir des sauvegardes correspondantes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 @Override
 public void setAccessToken(Serializable accessToken) {
	httpSession.setAttribute(O365_ACCESS_TOKEN, accessToken);
	
	PortalPreferences userPreference = getUserPreference();
	if(userPreference != null){
		userPreference.setValue(NAMESPACE, REFRESH_TOKEN, getRefreshToken());
	
		Integer expiresIn = getOAuthAccessToken().getExpiresIn();
		Instant expiresAt = Instant.now().plusSeconds(expiresIn-30);
		userPreference.setValue(NAMESPACE, EXPIRES_AT, String.valueOf(expiresAt.getEpochSecond()));
		httpSession.setAttribute(O365_ACCESS_TOKEN+EXPIRES_AT, expiresAt.getEpochSecond());
	}
 }

 

Il faut porter une attention particulière à deux éléments. Premièrement, récupérer la session HTTP est différent à partir d’un portletRequest ou d’un httpRequest. Il y a même des différences entre le httpRequest qui provient du filtre ou du thème. Le constructeur prend en charge ces distinctions :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 public O365AuthenticationLiferayAdapter(HttpServletRequest request) {
	HttpServletRequest originalServletRequest = PortalUtil.getOriginalServletRequest(request);
	this.httpSession = originalServletRequest.getSession();
	
	StringBuffer requestURL = request.getRequestURL();
	requestURL.delete(requestURL.indexOf("/", 8), Integer.MAX_VALUE);
	requestURL.append("/o/o365/login");
	
	callBackUrl = requestURL.toString();
 }

 

Le second problème concerne l’information d’expiration. Le service d’authentification nous envoie le nombre de secondes ou l’accessToken est valide, pas le moment auquel il va expirer. Nous devons donc calculer le moment de l’expiration quand nous le recevons. Comme précaution, je retranche quelques secondes supplémentaires afin de demander un nouveau token si nous sommes trop près de l’expiration du token actuel.

[Bonus] Authentifier automatiquement l’utilisateur

Dans un environnement d’entreprise, nous avons habituellement beaucoup de systèmes qui interagissent ensemble et où l’utilisateur doit s’authentifier. Dans la plupart des cas, il y a un SSO en place pour limiter le nombre d’applications ou les utilisateurs doivent s’authentifier. En particulier, avec Office 365, si c’est l’outil standard de l’entreprise, les utilisateurs ont tendance à toujours être authentifiés. Alors, pourquoi leur demander de se reconnecter manuellement à partir du portail. Un simple post login action permet de déclencher le processus d’authentification une fois l’utilisateur connecter à Liferay :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 @Component(
    immediate = true,
    property = {"key=" + PropsKeys.LOGIN_EVENTS_POST},
    service = LifecycleAction.class )
 public final class PostLoginAction extends Action
 {
	@Override
	public void run(HttpServletRequest request, HttpServletResponse response) {
	    try{
	        String redirect = ParamUtil.getString(request, "_com_liferay_login_web_portlet_LoginPortlet_redirect","/");
	        String backUrl = HttpUtil.encodePath(redirect);
	        response.sendRedirect("/o/o365/login?backURL="+backUrl);
	    }catch (Exception e){
	        throw new RuntimeException("Error when redirecting user", e);
	    }
	}
 }

 

Cela redirige automatiquement les utilisateurs vers le filtre de connexion à Office 365 en incluant l’URL de redirection de l’authentification Liferay comme BackURL. De cette manière, chaque fois qu’un utilisateur se connecte au portail, il est aussi automatiquement connecté via Oauth 2.0. Dans un environnement ou les utilisateurs sont pratiquement toujours connecté à Office 365, ils ne verront qu’une page blanche pour quelques secondes et pourrons ensuite profité de toutes les magnifiques intégrations qui leur sont fournies.

Mots de la fin

Cela complète la manière dont nous avons intégré un service externe via OAuth 2.0 dans un portail Liferay et plus généralement dans une application web java. Le code complet et fonctionnel est disponible ici : https://github.com/savoirfairelinux/office-365-integration. De plus, le plugin qui a mené à l’écriture de cet article est maintenant disponible sur le marketplace liferay.

Laisser un commentaire

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

Ce site utilise Akismet pour réduire le pourriel. En savoir plus sur comment les données de vos commentaires sont utilisées.


Articles similaires

Image of an arrow
Fermer
Fermer