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 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’integrazione del servizio SSO con Spring Security è * realizzata attraverso l’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’autenticazione SSO è condiviso tra tutte le * applicazioni autenticate attraverso lo stesso server SSO. Il cookie per * l’autenticazione persistente è normalmente privato di una sola * applicazione, a meno che, naturalmente, l’autenticazione * dell’applicazione sia gestita da un server SSO: in questo caso anche * l’autenticazione persistente in un’applicazione deve essere * condivisa con le altre applicazioni gestite dallo stesso server SSO. * <LI>Il cookie per l’autenticazione SSO scade con la chiusura del * client; il cookie per l’autenticazione persistente è, per * definizione, persistente con un certo periodo di scadenza. * </OL> * * <P>Il componente {@code SsoAuthenticationService} prevede anche * l’attivazione del servizio di autenticazione persistente delegando la * gestione ad un altro {@linkplain #setRememberMeServices servizio} che * implementa l’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’autenticazione persistente. Il * valore della costante è <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’autenticazione. * * @param obj Oggetto. */ @Required public void setAuthenticationManager(AuthenticationManager obj) { myAuthManager = obj; } /** * Imposta il servizio di persistenza dell’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’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 è 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; } }