/** * Updates the user's Shibboleth session with authentication information. If no session exists a * new one will be created. * * @param loginContext current login context * @param authenticationSubject subject created from the authentication method * @param authenticationMethod the method used to authenticate the subject * @param authenticationInstant the time of authentication * @param httpRequest current HTTP request * @param httpResponse current HTTP response */ protected void updateUserSession( LoginContext loginContext, Subject authenticationSubject, String authenticationMethod, DateTime authenticationInstant, HttpServletRequest httpRequest, HttpServletResponse httpResponse) { Principal authenticationPrincipal = authenticationSubject.getPrincipals().iterator().next(); LOG.debug("Updating session information for principal {}", authenticationPrincipal.getName()); Session idpSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE); if (idpSession == null) { LOG.debug("Creating shibboleth session for principal {}", authenticationPrincipal.getName()); idpSession = (Session) sessionManager.createSession(); loginContext.setSessionID(idpSession.getSessionID()); addSessionCookie(httpRequest, httpResponse, idpSession); } // Merge the information in the current session subject with the information from the // login handler subject idpSession.setSubject(mergeSubjects(idpSession.getSubject(), authenticationSubject)); // Check if an existing authentication method with no updated timestamp was used (i.e. SSO // occurred); // if not record the new information AuthenticationMethodInformation authnMethodInfo = idpSession.getAuthenticationMethods().get(authenticationMethod); if (authnMethodInfo == null || authenticationInstant != null) { LOG.debug( "Recording authentication and service information in Shibboleth session for principal: {}", authenticationPrincipal.getName()); LoginHandler loginHandler = handlerManager.getLoginHandlers().get(loginContext.getAttemptedAuthnMethod()); DateTime authnInstant = authenticationInstant; if (authnInstant == null) { authnInstant = new DateTime(); } authnMethodInfo = new AuthenticationMethodInformationImpl( idpSession.getSubject(), authenticationPrincipal, authenticationMethod, authnInstant, loginHandler.getAuthenticationDuration()); } loginContext.setAuthenticationMethodInformation(authnMethodInfo); idpSession .getAuthenticationMethods() .put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo); sessionManager.indexSession(idpSession, idpSession.getPrincipalName()); ServiceInformation serviceInfo = new ServiceInformationImpl( loginContext.getRelyingPartyId(), new DateTime(), authnMethodInfo); idpSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo); }
/** * Filters out any login handler based on the requirement for forced authentication. * * <p>During forced authentication any handler that has not previously been used to authenticate * the user or any handlers that have been and support force re-authentication may be used. Filter * out any of the other ones. * * @param idpSession user's current IdP session * @param loginContext current login context * @param loginHandlers login handlers to filter * @throws ForceAuthenticationException thrown if no handlers remain after filtering */ protected void filterByForceAuthentication( Session idpSession, LoginContext loginContext, Map<String, LoginHandler> loginHandlers) throws ForceAuthenticationException { LOG.debug("Forced authentication is required, filtering possible login handlers accordingly"); ArrayList<AuthenticationMethodInformation> activeMethods = new ArrayList<AuthenticationMethodInformation>(); if (idpSession != null) { activeMethods.addAll(idpSession.getAuthenticationMethods().values()); } loginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX); LoginHandler loginHandler; for (AuthenticationMethodInformation activeMethod : activeMethods) { loginHandler = loginHandlers.get(activeMethod.getAuthenticationMethod()); if (loginHandler != null && !loginHandler.supportsForceAuthentication()) { for (String handlerSupportedMethods : loginHandler.getSupportedAuthenticationMethods()) { LOG.debug( "Removing LoginHandler {}, it does not support forced re-authentication", loginHandler.getClass().getName()); loginHandlers.remove(handlerSupportedMethods); } } } LOG.debug( "Authentication handlers remaining after forced authentication requirement filtering: {}", loginHandlers); if (loginHandlers.isEmpty()) { LOG.info("Force authentication requested but no login handlers available to support it"); throw new ForceAuthenticationException(); } }
/** * Filters out the previous session login handler if there is no existing IdP session, no active * authentication methods, or if at least one of the active authentication methods do not match * the requested authentication methods. * * @param supportedLoginHandlers login handlers supported by the authentication engine for this * request, never null * @param idpSession current IdP session, may be null if no session currently exists * @param loginContext current login context, never null */ protected void filterPreviousSessionLoginHandler( Map<String, LoginHandler> supportedLoginHandlers, Session idpSession, LoginContext loginContext) { if (!supportedLoginHandlers.containsKey(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX)) { return; } if (idpSession == null) { LOG.debug( "Filtering out previous session login handler because there is no existing IdP session"); supportedLoginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX); return; } Collection<AuthenticationMethodInformation> currentAuthnMethods = idpSession.getAuthenticationMethods().values(); Iterator<AuthenticationMethodInformation> methodItr = currentAuthnMethods.iterator(); while (methodItr.hasNext()) { AuthenticationMethodInformation info = methodItr.next(); if (info.isExpired()) { methodItr.remove(); } } if (currentAuthnMethods.isEmpty()) { LOG.debug( "Filtering out previous session login handler because there are no active authentication methods"); supportedLoginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX); return; } List<String> requestedMethods = loginContext.getRequestedAuthenticationMethods(); if (requestedMethods != null && !requestedMethods.isEmpty()) { boolean retainPreviousSession = false; for (AuthenticationMethodInformation currentAuthnMethod : currentAuthnMethods) { if (loginContext .getRequestedAuthenticationMethods() .contains(currentAuthnMethod.getAuthenticationMethod())) { retainPreviousSession = true; break; } } if (!retainPreviousSession) { LOG.debug( "Filtering out previous session login handler, no active authentication methods match required methods"); supportedLoginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX); return; } } }
/** * Adds an IdP session cookie to the outbound response. * * @param httpRequest current request * @param httpResponse current response * @param userSession user's session */ protected void addSessionCookie( HttpServletRequest httpRequest, HttpServletResponse httpResponse, Session userSession) { httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, userSession); byte[] remoteAddress = httpRequest.getRemoteAddr().getBytes(); byte[] sessionId = userSession.getSessionID().getBytes(); String signature = null; try { MessageDigest digester = MessageDigest.getInstance("SHA"); digester.update(userSession.getSessionSecret()); digester.update(remoteAddress); digester.update(sessionId); signature = Base64.encodeBytes(digester.digest()); } catch (GeneralSecurityException e) { LOG.error("Unable to compute signature over session cookie material", e); } LOG.debug("Adding IdP session cookie to HTTP response"); StringBuilder cookieValue = new StringBuilder(); cookieValue.append(Base64.encodeBytes(remoteAddress, Base64.DONT_BREAK_LINES)).append("|"); cookieValue.append(Base64.encodeBytes(sessionId, Base64.DONT_BREAK_LINES)).append("|"); cookieValue.append(signature); String cookieDomain = HttpServletHelper.getCookieDomain(context); Cookie sessionCookie = new Cookie(IDP_SESSION_COOKIE_NAME, HTTPTransportUtils.urlEncode(cookieValue.toString())); sessionCookie.setVersion(1); if (cookieDomain != null) { sessionCookie.setDomain(cookieDomain); } sessionCookie.setPath( "".equals(httpRequest.getContextPath()) ? "/" : httpRequest.getContextPath()); sessionCookie.setSecure(httpRequest.isSecure()); httpResponse.addCookie(sessionCookie); }
/** * Begins the authentication process. Determines if forced re-authentication is required or if an * existing, active, authentication method is sufficient. Also determines, when authentication is * required, which handler to use depending on whether passive authentication is required. * * @param loginContext current login context * @param httpRequest current HTTP request * @param httpResponse current HTTP response */ protected void startUserAuthentication( LoginContext loginContext, HttpServletRequest httpRequest, HttpServletResponse httpResponse) { LOG.debug("Beginning user authentication process."); try { Session idpSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE); if (idpSession != null) { LOG.debug("Existing IdP session available for principal {}", idpSession.getPrincipalName()); } Map<String, LoginHandler> possibleLoginHandlers = determinePossibleLoginHandlers(idpSession, loginContext); // Filter out possible candidate login handlers by forced and passive authentication // requirements if (loginContext.isForceAuthRequired()) { filterByForceAuthentication(idpSession, loginContext, possibleLoginHandlers); } if (loginContext.isPassiveAuthRequired()) { filterByPassiveAuthentication(idpSession, loginContext, possibleLoginHandlers); } LoginHandler loginHandler = selectLoginHandler(possibleLoginHandlers, loginContext, idpSession); loginContext.setAuthenticationAttempted(); loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest)); // Send the request to the login handler HttpServletHelper.bindLoginContext( loginContext, storageService, getServletContext(), httpRequest, httpResponse); loginHandler.login(httpRequest, httpResponse); } catch (AuthenticationException e) { loginContext.setAuthenticationFailure(e); returnToProfileHandler(httpRequest, httpResponse); } }
/** * Selects a login handler from a list of possible login handlers that could be used for the * request. * * @param possibleLoginHandlers list of possible login handlers that could be used for the request * @param loginContext current login context * @param idpSession current IdP session, if one exists * @return the login handler to use for this request * @throws AuthenticationException thrown if no handler can be used for this request */ protected LoginHandler selectLoginHandler( Map<String, LoginHandler> possibleLoginHandlers, LoginContext loginContext, Session idpSession) throws AuthenticationException { LOG.debug("Selecting appropriate login handler from filtered set {}", possibleLoginHandlers); LoginHandler loginHandler; if (idpSession != null && possibleLoginHandlers.containsKey(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX)) { LOG.debug("Authenticating user with previous session LoginHandler"); loginHandler = possibleLoginHandlers.get(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX); for (AuthenticationMethodInformation authnMethod : idpSession.getAuthenticationMethods().values()) { if (authnMethod.isExpired()) { continue; } if (loginContext.getRequestedAuthenticationMethods().isEmpty() || loginContext .getRequestedAuthenticationMethods() .contains(authnMethod.getAuthenticationMethod())) { LOG.debug( "Basing previous session authentication on active authentication method {}", authnMethod.getAuthenticationMethod()); loginContext.setAttemptedAuthnMethod(authnMethod.getAuthenticationMethod()); loginContext.setAuthenticationMethodInformation(authnMethod); return loginHandler; } } } if (loginContext.getDefaultAuthenticationMethod() != null && possibleLoginHandlers.containsKey(loginContext.getDefaultAuthenticationMethod())) { loginHandler = possibleLoginHandlers.get(loginContext.getDefaultAuthenticationMethod()); loginContext.setAttemptedAuthnMethod(loginContext.getDefaultAuthenticationMethod()); } else { Entry<String, LoginHandler> chosenLoginHandler = possibleLoginHandlers.entrySet().iterator().next(); loginContext.setAttemptedAuthnMethod(chosenLoginHandler.getKey()); loginHandler = chosenLoginHandler.getValue(); } LOG.debug( "Authenticating user with login handler of type {}", loginHandler.getClass().getName()); return loginHandler; }
/** * If forced authentication was required this method checks to ensure that the re-authenticated * subject contains a principal name that is equal to the principal name associated with the * authentication method. If this is the first time the subject has authenticated with this method * than this check always passes. * * @param idpSession user's IdP session * @param authnMethod method used to authenticate the user * @param subject subject that was authenticated * @throws AuthenticationException thrown if this check fails */ protected void validateForcedReauthentication( Session idpSession, String authnMethod, Subject subject) throws AuthenticationException { if (idpSession != null) { AuthenticationMethodInformation authnMethodInfo = idpSession.getAuthenticationMethods().get(authnMethod); if (authnMethodInfo != null) { boolean princpalMatch = false; for (Principal princpal : subject.getPrincipals()) { if (authnMethodInfo.getAuthenticationPrincipal().equals(princpal)) { princpalMatch = true; break; } } if (!princpalMatch) { throw new ForceAuthenticationException( "Authenticated principal does not match previously authenticated principal"); } } } }