it.scoppelletti.programmerpower.web.security.SsoAuthenticationService.java Source code

Java tutorial

Introduction

Here is the source code for it.scoppelletti.programmerpower.web.security.SsoAuthenticationService.java

Source

/*
 * Copyright (C) 2011 Dario Scoppelletti, <http://www.scoppelletti.it/>.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.scoppelletti.programmerpower.web.security;

import java.io.*;
import javax.servlet.http.*;
import org.slf4j.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.security.authentication.*;
import org.springframework.security.cas.web.*;
import org.springframework.security.core.*;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.logout.*;
import it.scoppelletti.programmerpower.*;
import it.scoppelletti.programmerpower.reflect.*;
import it.scoppelletti.programmerpower.security.*;
import it.scoppelletti.programmerpower.types.*;
import it.scoppelletti.programmerpower.web.*;

/**
 * Servizio di autenticazione <ACRONYM TITLE="Single Sign-On">SSO</ACRONYM>.
 * 
 * <P>L&rsquo;integrazione del servizio SSO con Spring Security &egrave;
 * realizzata attraverso l&rsquo;interfaccia {@code RememberMeServices}:
 * effettivamente sia un servizio SSO che un servizio di autenticazione
 * persistente si basano su un cookie registrato sul client e le differenze di
 * riducono praticamente alle seguenti:</P>
 * 
 * <OL>
 * <LI>Il cookie per l&rsquo;autenticazione SSO &egrave; condiviso tra tutte le
 * applicazioni autenticate attraverso lo stesso server SSO. Il cookie per
 * l&rsquo;autenticazione persistente &egrave; normalmente privato di una sola
 * applicazione, a meno che, naturalmente, l&rsquo;autenticazione
 * dell&rsquo;applicazione sia gestita da un server SSO: in questo caso anche 
 * l&rsquo;autenticazione persistente in un&rsquo;applicazione deve essere
 * condivisa con le altre applicazioni gestite dallo stesso server SSO.
 * <LI>Il cookie per l&rsquo;autenticazione SSO scade con la chiusura del
 * client; il cookie per l&rsquo;autenticazione persistente &egrave;, per 
 * definizione, persistente con un certo periodo di scadenza.
 * </OL>
 * 
 * <P>Il componente {@code SsoAuthenticationService} prevede anche 
 * l&rsquo;attivazione del servizio di autenticazione persistente delegando la
 * gestione ad un altro {@linkplain #setRememberMeServices servizio} che
 * implementa l&rsquo;interfaccia {@code RememberMeServices}.</P> 
 * 
 * @since 1.0.0
 */
@Final
public class SsoAuthenticationService implements AuthenticationService, RememberMeServices, LogoutHandler {

    /**
     * Attributo di sessione
     * {@code it.scoppelletti.programmerpower.web.security.TGT}:
     * Ticket di autenticazione.
     */
    public static final String ATTR_TICKETGRANTINGTICKET = "it.scoppelletti.programmerpower.web.security.TGT";

    /**
     * Suffisso applicato ai ticket di autenticazione per identificarli come
     * ticket di autenticatione per l&rsquo;autenticazione persistente. Il
     * valore della costante &egrave; <CODE>{@value}</CODE>.  
     */
    public static final String TICKET_SUFFIX = "-SSO";

    private static final Logger myLogger = LoggerFactory.getLogger(SsoAuthenticationService.class);

    private CasClient myCasClient;
    private AuthenticationManager myAuthManager;
    private RememberMeServices myRememberMeServices;
    private WebAuthenticationDetailsSource myAuthDetailsSource;

    /**
     * Costruttore.
     */
    public SsoAuthenticationService() {
        myRememberMeServices = new NullRememberMeServices();
        myAuthDetailsSource = new WebAuthenticationDetailsSource();
    }

    /**
     * Imposta il client CAS.
     * 
     * @param obj Oggetto.
     */
    @Required
    public void setCasClient(CasClient obj) {
        myCasClient = obj;
    }

    /**
     * Imposta il gestore dell&rsquo;autenticazione.
     * 
     * @param obj Oggetto.
     */
    @Required
    public void setAuthenticationManager(AuthenticationManager obj) {
        myAuthManager = obj;
    }

    /**
     * Imposta il servizio di persistenza dell&rsquo;autenticazione.
     * 
     * @param obj Oggetto.
     * @it.scoppelletti.tag.default {@code org.springframework.security.web.authentication.NullRememberMeServices}
     */
    public void setRememberMeServices(RememberMeServices obj) {
        if (obj == null) {
            throw new ArgumentNullException("obj");
        }

        myRememberMeServices = obj;
    }

    public String newAuthenticationTicket(HttpServletRequest req, HttpServletResponse resp, String userCode,
            SecureString pwd) {
        String tgt, ticket;
        HttpSession session;
        AttributeMap sessionMap;

        if (Strings.isNullOrEmpty(userCode)) {
            throw new ArgumentNullException("userCode");
        }
        if (Values.isNullOrEmpty(pwd)) {
            throw new ArgumentNullException("pwd");
        }
        if (req == null) {
            throw new ArgumentNullException("req");
        }
        if (resp == null) {
            throw new ArgumentNullException("resp");
        }
        if (myCasClient == null) {
            throw new PropertyNotSetException(toString(), "casClient");
        }

        myLogger.trace("Calling method newAuthenticationTicket.");
        try {
            tgt = myCasClient.newTicketGrantingTicket(userCode, pwd);
            ticket = myCasClient.newServiceTicket(tgt);
        } catch (IOException ex) {
            throw new AuthenticationServiceException(ApplicationException.toString(ex), ex);
        }

        session = req.getSession(true);
        sessionMap = WebUtils.getSynchronizedAttributeMap(session);
        sessionMap.setAttribute(SsoAuthenticationService.ATTR_TICKETGRANTINGTICKET, tgt);

        myLogger.debug("New ticket {} for session {}.", ticket, session.getId());

        return ticket;
    }

    /**
     * Login automatico.
     * 
     * @param  req  Richiesta.
     * @param  resp Risposta.
     * @return      Token autenticato. Se l&rsquo;autenticazione non avviene,
     *              restituisce {@code null}.  
     */
    public Authentication autoLogin(HttpServletRequest req, HttpServletResponse resp) {
        Authentication authToken = null;

        if (req == null) {
            throw new ArgumentNullException("req");
        }
        if (myCasClient == null) {
            throw new PropertyNotSetException(toString(), "casClient");
        }
        if (myAuthManager == null) {
            throw new PropertyNotSetException(toString(), "AuthenticationManager");
        }
        if (myAuthDetailsSource == null) {
            throw new PropertyNotSetException(toString(), "AuthenticationDetailsSource");
        }

        myLogger.trace("Calling method autoLogin.");
        try {
            authToken = singleSignOn(req, resp);
        } catch (Exception ex) {
            myLogger.error("Single Sing-On failed.", ex);
        }
        if (authToken != null) {
            // L'autenticazione e' gia' stata eseguita interattivamente da 
            // un'altra applicazione:
            // Ignoro l'eventuale servizio di autenticazione persistente.
            myLogger.debug("Single Sign-On occurred.");
            return authToken;
        }

        try {
            authToken = myRememberMeServices.autoLogin(req, resp);
        } catch (Exception ex) {
            myLogger.error("Remember Me authentication failed.", ex);
        }
        if (authToken != null) {
            myLogger.debug("RememberMe authentication occurred.");
        }

        return authToken;
    }

    /**
     * Autenticazione SSO.
     * 
     * @param  req  Richiesta.
     * @param  resp Risposta.
     * @return      Token autenticato. Se il SSO non avviene, restituisce
     *              {@code null}.  
     */
    private Authentication singleSignOn(HttpServletRequest req, HttpServletResponse resp) {
        String tgt, ticket;
        HttpSession session;
        Authentication result;
        AbstractAuthenticationToken authRequest;

        tgt = getTicketGrantingTicket(req, resp);
        if (Strings.isNullOrEmpty(tgt)) {
            return null;
        }

        try {
            ticket = myCasClient.newServiceTicket(tgt);
        } catch (IOException ex) {
            myCasClient.removeTicketGrantingTicket(req, resp);
            throw new AuthenticationServiceException("Single Sing-On failed.", ex);
        }

        session = req.getSession(true);
        myLogger.debug("New ticket {} for session {}.", ticket, session.getId());

        authRequest = new UsernamePasswordAuthenticationToken(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER,
                ticket);
        authRequest.setDetails(myAuthDetailsSource.buildDetails(req));

        result = myAuthManager.authenticate(authRequest);
        if (result == null) {
            return null;
        }

        myCasClient.addAuthenticatedSession(ticket, session);

        return result;
    }

    /**
     * Gestore del successo del login interattivo.
     * 
     * @param  req            Richiesta.
     * @param  resp           Risposta.
     * @param  authentication Token autenticato. 
     */
    public void loginSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) {
        String tgt;
        HttpSession session;
        AttributeMap sessionMap;

        if (req == null) {
            throw new ArgumentNullException("req");
        }
        if (resp == null) {
            throw new ArgumentNullException("resp");
        }
        if (myCasClient == null) {
            throw new PropertyNotSetException(toString(), "casClient");
        }

        myLogger.trace("Calling method loginSuccess.");
        try {
            myRememberMeServices.loginSuccess(req, resp, authentication);
        } catch (Exception ex) {
            myLogger.error(ApplicationException.toString(ex), ex);
        }

        session = req.getSession(false);
        if (session != null) {
            sessionMap = WebUtils.getSynchronizedAttributeMap(session);
            tgt = (String) sessionMap.getAttribute(SsoAuthenticationService.ATTR_TICKETGRANTINGTICKET);
        } else {
            sessionMap = null; // avoid warning
            tgt = null;
        }
        if (Strings.isNullOrEmpty(tgt)) {
            // Sara' nuovamente richiesta l'autenticazione
            myLogger.error("SSO TGT not set in session attributes.");
            return;
        }

        tgt = tgt.concat(SsoAuthenticationService.TICKET_SUFFIX);
        myCasClient.addTicketGrantingTicket(req, resp, tgt);

        sessionMap.removeAttribute(SsoAuthenticationService.ATTR_TICKETGRANTINGTICKET);
    }

    /**
     * Gestore del fallimento del login.
     * 
     * @param  req  Richiesta.
     * @param  resp Risposta.
     */
    public void loginFail(HttpServletRequest req, HttpServletResponse resp) {
        HttpSession session;
        AttributeMap sessionMap;

        if (req == null) {
            throw new ArgumentNullException("req");
        }
        if (resp == null) {
            throw new ArgumentNullException("resp");
        }

        myLogger.trace("Calling method loginFail.");
        session = req.getSession(false);
        if (session != null) {
            sessionMap = WebUtils.getSynchronizedAttributeMap(session);
            sessionMap.removeAttribute(SsoAuthenticationService.ATTR_TICKETGRANTINGTICKET);
        }

        try {
            myRememberMeServices.loginFail(req, resp);
        } catch (Exception ex) {
            myLogger.error(ApplicationException.toString(ex), ex);
        }
    }

    /**
     * Gestore del logout.
     * 
     * @param req            Richiesta.
     * @param resp           Risposta.
     * @param authentication Token autenticato.
     */
    public void logout(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) {
        String tgt;
        LogoutHandler logoutHandler;

        if (req == null) {
            throw new ArgumentNullException("req");
        }
        if (resp == null) {
            throw new ArgumentNullException("resp");
        }
        if (myCasClient == null) {
            throw new PropertyNotSetException(toString(), "casClient");
        }

        myLogger.trace("Calling method logout.");
        if (myRememberMeServices instanceof LogoutHandler) {
            logoutHandler = (LogoutHandler) myRememberMeServices;
            try {
                logoutHandler.logout(req, resp, authentication);
            } catch (Exception ex) {
                myLogger.error(ApplicationException.toString(ex), ex);
            }
        }

        tgt = getTicketGrantingTicket(req, resp);
        if (Strings.isNullOrEmpty(tgt)) {
            return;
        }

        myCasClient.removeTicketGrantingTicket(req, resp);
        try {
            myCasClient.destroyTicketGrantingTicket(req, resp, tgt);
        } catch (Exception ex) {
            myLogger.error(ApplicationException.toString(ex), ex);
        }
    }

    /**
     * Restituisce il ticket di autenticazione registrato come cookie.
     * 
     * @param  req  Richiesta.
     * @param  resp Risposta.
     * @return      Valore. Se il ticket non &egrave; stato registrato,
     *              restituisce {@code null}.
     */
    private String getTicketGrantingTicket(HttpServletRequest req, HttpServletResponse resp) {
        String value;

        value = myCasClient.getTicketGrantingTicket(req, resp);
        if (Strings.isNullOrEmpty(value) || value.length() <= SsoAuthenticationService.TICKET_SUFFIX.length()
                || !value.endsWith(SsoAuthenticationService.TICKET_SUFFIX)) {
            myLogger.debug("SSO TGT not stored in cookies.");
            return null;
        }

        value = value.substring(0, value.length() - SsoAuthenticationService.TICKET_SUFFIX.length());

        return value;
    }
}