Java tutorial
/* * 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 javax.servlet.http.*; import org.slf4j.*; import org.springframework.beans.factory.annotation.*; import org.springframework.security.core.*; import org.springframework.security.core.userdetails.*; import org.springframework.security.web.authentication.rememberme.*; import it.scoppelletti.programmerpower.*; import it.scoppelletti.programmerpower.security.*; import it.scoppelletti.programmerpower.reflect.*; import it.scoppelletti.programmerpower.types.*; /** * Servizi per l’autenticazione persistente in ambito * <ACRONYM TITLE="Single Sign-On">SSO</ACRONYM>. * * <P>La classe {@code SsoRememberMeServices} estende le funzionalità * della classe Spring Security {@code PersistentTokenBasedRememberMeServices} * con la possibilità di configurare il * {@linkplain #setCookieDomain dominio} e il * {@linkplain #setCookiePath percorso} di visibilità del cookie * utilizzato per l’autenticazione persistente; la classe base infatti * utilizza un cookie visibile alla sola applicazione che lo ha registrato e che * quindi non è adatto per un’autenticazione SSO.</P> * * <P>Parallelamente all’autenticazione persistente di un utente per un * client, il componente {@code SsoRememberMeServices} esegue anche * un’autenticazione SSO <I>fittizia</I> che mantiene la collezione delle * sessioni autenticate in modo che funzioni correttamente anche il Single * Sign-Out.<BR> * Poichè la caratteristica di un’autenticazione persistente * è quella appunto di non richiedere le credenziali di autenticazione * per l’utente, la corrispondente autenticazione SSO può essere * eseguita solo con un {@linkplain #setUserName utente} dedicato del quale il * componente {@code SsoRememberMeServices} conosca la * {@linkplain #setPassword password}.</P> * * @see it.scoppelletti.programmerpower.web.security.SingleSignOutFilter * @see it.scoppelletti.programmerpower.web.security.SingleSignOutSessionListener * @see <A HREF="{@docRoot}/../reference/wui/init.html#idRememberme" * TARGET="_top">Autenticazione persistente</A> * @since 1.0.0 */ @Final public class SsoRememberMeServices extends PersistentTokenBasedRememberMeServices { /** * Valore di default del percorso di visibilità del cookie per * l’autenticazione persistente. Il valore della costante è * <CODE>{@value}</CODE>. * * @see #setCookiePath */ public static final String DEF_COOKIEPATH = "/"; /** * Suffisso applicato ai ticket di autenticazione per identificarli come * ticket di autenticatione per l’autenticazione persistente. Il * valore della costante è <CODE>{@value}</CODE>. */ public static final String TICKET_SUFFIX = "-RM"; /** * Nome della proprietà di ambiente sulla quale può essere * impostato il nome del cookie nel quale è memorizzato il token per * l’autenticazione persistente. Il valore della costante è * <CODE>{@value}</CODE>. * * @it.scoppelletti.tag.default {@code it.scoppelletti.RMC} * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà di ambiente</A> */ public static final String PROP_COOKIE = "it.scoppelletti.programmerpower.web.security.SsoRememberMeServices.cookie"; /** * Nome della proprietà di ambiente sulla quale può essere * impostato il periodo di validità (in secondi) del token per * l’autenticazione persistente. Il valore della costante è * <CODE>{@value}</CODE>. * * @it.scoppelletti.tag.default 3 mesi (= 7889231 secondi). * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà di ambiente</A> */ public static final String PROP_TIMEOUT = "it.scoppelletti.programmerpower.web.security.SsoRememberMeServices.timeout"; /** * Nome della proprietà di ambiente sulla quale deve essere impostata * la chiave di controllo contro la manomissione del token del servizio di * autenticazione persistente. Il valore della costante è * <CODE>{@value}</CODE>. * * @it.scoppelletti.tag.default {@code changeit} * @see #SsoRememberMeServices * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà di ambiente</A> */ public static final String PROP_KEY = "it.scoppelletti.programmerpower.web.security.SsoRememberMeServices.key"; /** * Nome della proprietà di ambiente sulla quale può essere * impostato il nome dell’utente che rappresenta * l’autenticazione persistente. Il valore della costante è * <CODE>{@value}</CODE>. * * @it.scoppelletti.tag.default {@code rememberme} * @see #setUserName * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà di ambiente</A> */ public static final String PROP_USERNAME = "it.scoppelletti.programmerpower.web.security.SsoRememberMeServices.user"; /** * Nome della proprietà di ambiente sulla quale deve essere impostata * la password dell’utente che rappresenta l’autenticazione * persistente. Il valore della costante è * <CODE>{@value}</CODE>. * * @it.scoppelletti.tag.default {@code changeit} * @see #setPassword * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà di ambiente</A> */ public static final String PROP_PASSWORD = "it.scoppelletti.programmerpower.web.security.SsoRememberMeServices.pwd"; private static final Logger myLogger = LoggerFactory.getLogger(SsoRememberMeServices.class); private String myCookieDomain; private String myCookiePath; private boolean mySecureCookie; private String myUserName; private String myPwd; private CasClient myCasClient; /** * Costruttore. * * @param key Chiave di controllo contro la manomissione del * token del servizio di autenticazione * persistente. * @param userDetailsService Servizio di lettura dei dati degli utenti. * @param tokenRepository Repository dei token del servizio di * autenticazione persistente. */ public SsoRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { super(key, userDetailsService, tokenRepository); myCookiePath = SsoRememberMeServices.DEF_COOKIEPATH; } /** * Imposta il dominio di visibilità dei cookie. * * @param value Valore (es. {@code .scoppelletti.it}). */ public void setCookieDomain(String value) { myCookieDomain = value; } /** * Imposta il percorso di visibilità dei cookie. * * @param value Valore. * @it.scoppelletti.tag.default {@code "/"} */ public void setCookiePath(String value) { if (Strings.isNullOrEmpty(value)) { throw new ArgumentNullException("value"); } myCookiePath = value; } /** * Imposta l’indicatore di trasmissione dei cookie solo attraverso * connessioni sicure. * * @param value Valore. */ @Override public void setUseSecureCookie(boolean value) { // La classe base non pubblica il metodo accessore di lettura: // Posso solo implementare una versione prevalente del metodo accessore // di scrittura per memorizzare il valore anche in un campo privato // locale. super.setUseSecureCookie(value); mySecureCookie = value; } /** * Imposta il nome dell’utente che rappresenta l’autenticazione * persistente. * * @param value Valore. */ @Required public void setUserName(String value) { myUserName = value; } /** * Imposta la password dell’utente che rappresenta * l’autenticazione persistente. * * @param value Valore. */ @Required public void setPassword(String value) { myPwd = value; } /** * Imposta il client CAS. * * @param obj Oggetto. */ @Required public void setCasClient(CasClient obj) { myCasClient = obj; } /** * Valida l’autenticazione persistente. * * @param cookieTokens Componenti del cookie per l’autenticazione * persistente. * @param req Richiesta. * @param resp Risposta. * @return Utente autenticato. */ @Override protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest req, HttpServletResponse resp) { boolean newTGT; String tgt, ticket; UserDetails user; HttpSession session; AuthenticationException authEx; if (Strings.isNullOrEmpty(myUserName)) { throw new PropertyNotSetException(toString(), "userName"); } if (Strings.isNullOrEmpty(myPwd)) { throw new PropertyNotSetException(toString(), "password"); } if (myCasClient == null) { throw new PropertyNotSetException(toString(), "casClient"); } user = super.processAutoLoginCookie(cookieTokens, req, resp); tgt = getTicketGrantingTicket(req, resp); newTGT = Strings.isNullOrEmpty(tgt); try { if (newTGT) { tgt = myCasClient.newTicketGrantingTicket(myUserName, new SecureString(myPwd)); } ticket = myCasClient.newServiceTicket(tgt); } catch (Exception ex) { authEx = new RememberMeAuthenticationException(ApplicationException.toString(ex)); authEx.initCause(ex); throw authEx; } session = req.getSession(true); myLogger.debug("New ticket {} for session {}.", ticket, session.getId()); if (newTGT) { tgt = tgt.concat(SsoRememberMeServices.TICKET_SUFFIX); myCasClient.addTicketGrantingTicket(req, resp, tgt); } myCasClient.addAuthenticatedSession(ticket, session); return user; } /** * Registra il cookie per l’autenticazione persistente. * * @param tokens Token codificati nel cookie. * @param maxAge Scadenza. * @param req Richiesta. * @param resp Risposta. */ @Override protected void setCookie(String[] tokens, int maxAge, HttpServletRequest req, HttpServletResponse resp) { String value; Cookie cookie; value = encodeCookie(tokens); cookie = buildCookie(value, maxAge); cookie.setSecure(mySecureCookie); resp.addCookie(cookie); } /** * Rimuove il cookie per l’autenticazione persistente. * * @param req Richiesta. * @param resp Risposta. */ @Override protected void cancelCookie(HttpServletRequest req, HttpServletResponse resp) { Cookie cookie; cookie = buildCookie(null, 0); resp.addCookie(cookie); } /** * Inizializza il cookie per l’autenticazione persistente. * * @param value Valore. * @param maxAge Scadenza. * @return Oggetto. */ private Cookie buildCookie(String value, int maxAge) { Cookie cookie; cookie = new Cookie(getCookieName(), value); if (myCookieDomain != null) { cookie.setDomain(myCookieDomain); } cookie.setPath(myCookiePath); cookie.setMaxAge(maxAge); return cookie; } /** * Gestore del logout. * * @param req Richiesta. * @param resp Risposta. * @param authentication Token autenticato. */ @Override public void logout(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) { String tgt; try { super.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 è 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() <= SsoRememberMeServices.TICKET_SUFFIX.length() || !value.endsWith(SsoRememberMeServices.TICKET_SUFFIX)) { myLogger.debug("RememberMe TGT not stored in cookies."); return null; } value = value.substring(0, value.length() - SsoRememberMeServices.TICKET_SUFFIX.length()); return value; } }