View Javadoc

    1   /*
    2    * Copyright 2008 University Corporation for Advanced Internet Development, Inc.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    * http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16
    17  package edu.internet2.middleware.shibboleth.idp.session;
    18
    19  import java.io.IOException;
    20  import java.security.GeneralSecurityException;
    21  import java.security.MessageDigest;
    22  import java.util.Arrays;
    23
    24  import javax.servlet.Filter;
    25  import javax.servlet.FilterChain;
    26  import javax.servlet.FilterConfig;
    27  import javax.servlet.ServletException;
    28  import javax.servlet.ServletRequest;
    29  import javax.servlet.ServletResponse;
    30  import javax.servlet.http.Cookie;
    31  import javax.servlet.http.HttpServletRequest;
    32
    33  import org.joda.time.DateTime;
    34  import org.opensaml.ws.transport.http.HTTPTransportUtils;
    35  import org.opensaml.xml.util.Base64;
    36  import org.opensaml.xml.util.DatatypeHelper;
    37  import org.slf4j.Logger;
    38  import org.slf4j.LoggerFactory;
    39  import org.slf4j.MDC;
    40
    41  import edu.internet2.middleware.shibboleth.common.session.SessionManager;
    42  import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
    43
    44  /**
    45   * A filter that adds the current users {@link Session} the request, if the user has a session.
    46   */
    47  public class IdPSessionFilter implements Filter {
    48
    49      /** Class Logger. */
    50      private final Logger log = LoggerFactory.getLogger(IdPSessionFilter.class);
    51
    52      /** Whether the client must always come back from the same address. */
    53      private boolean consistentAddress;
    54
    55      /** IdP session manager. */
    56      private SessionManager<Session> sessionManager;
    57
    58      /** {@inheritDoc} */
    59      public void destroy() {
    60
    61      }
    62
    63      /** {@inheritDoc} */
    64      public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
    65              ServletException {
    66          HttpServletRequest httpRequest = (HttpServletRequest) request;
    67
    68          Cookie sessionCookie = getIdPSessionCookie(httpRequest);
    69          Session idpSession = getUserSession(sessionCookie, httpRequest);
    70          if (idpSession != null) {
    71              log.trace("Updating IdP session activity time and adding session object to the request");
    72              idpSession.setLastActivityInstant(new DateTime());
    73              MDC.put("idpSessionId", idpSession.getSessionID());
    74              httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, idpSession);
    75          }
    76
    77          filterChain.doFilter(request, response);
    78      }
    79
    80      /** {@inheritDoc} */
    81      public void init(FilterConfig filterConfig) throws ServletException {
    82          String sessionManagerId = filterConfig.getInitParameter("sessionManagedId");
    83          if (DatatypeHelper.isEmpty(sessionManagerId)) {
    84              sessionManagerId = "shibboleth.SessionManager";
    85          }
    86
    87          sessionManager = (SessionManager<Session>) filterConfig.getServletContext().getAttribute(sessionManagerId);
    88
    89          String consistentAddressParam = filterConfig.getInitParameter("ensureConsistentClientAddress");
    90          if (DatatypeHelper.isEmpty(consistentAddressParam)) {
    91              consistentAddress = true;
    92          } else {
    93              consistentAddress = Boolean.parseBoolean(consistentAddressParam);
    94          }
    95      }
    96
    97      /**
    98       * Gets the IdP session cookie from the current request, if the user currently has a session.
    99       *
    100      * @param httpRequest current HTTP request
    101      *
    102      * @return the user's current IdP session cookie, if they have a current session, otherwise null
    103      */
    104     protected Cookie getIdPSessionCookie(HttpServletRequest httpRequest) {
    105         log.trace("Attempting to retrieve IdP session cookie.");
    106         Cookie[] requestCookies = httpRequest.getCookies();
    107
    108         if (requestCookies != null) {
    109             for (Cookie requestCookie : requestCookies) {
    110                 if (DatatypeHelper.safeEquals(requestCookie.getName(), AuthenticationEngine.IDP_SESSION_COOKIE_NAME)) {
    111                     log.trace("Found IdP session cookie.");
    112                     return requestCookie;
    113                 }
    114             }
    115         }
    116
    117         return null;
    118     }
    119
    120     /**
    121      * Gets the user session associated with a session cookie.
    122      *
    123      * @param sessionCookie the session cookie
    124      * @param httpRequest the current HTTP request
    125      *
    126      * @return the session associated with the cookie or null if there is no currently assoicated session
    127      */
    128     protected Session getUserSession(Cookie sessionCookie, HttpServletRequest httpRequest) {
    129         if (sessionCookie == null || DatatypeHelper.isEmpty(sessionCookie.getValue())) {
    130             return null;
    131         }
    132
    133         // index 0: remote address
    134         // index 1: session ID
    135         // index 2: Base64(HMAC(index 0 + index 1))
    136         String cookieValue = HTTPTransportUtils.urlDecode(sessionCookie.getValue());
    137         String[] valueComponents = cookieValue.split("\\|");
    138         if (valueComponents.length != 3) {
    139             log.warn("IdP session cookie has an improperly formated value: {}", cookieValue);
    140             return null;
    141         }
    142
    143         byte[] remoteAddressBytes = Base64.decode(valueComponents[0]);
    144         byte[] sessionIdBytes = Base64.decode(valueComponents[1]);
    145         byte[] signatureBytes = Base64.decode(valueComponents[2]);
    146
    147         String sessionId = new String(sessionIdBytes);
    148         Session userSession = sessionManager.getSession(sessionId);
    149
    150         if (userSession != null) {
    151             if (isCookieValid(httpRequest, remoteAddressBytes, sessionIdBytes, signatureBytes, userSession
    152                     .getSessionSecret())) {
    153                 return userSession;
    154             }
    155         } else {
    156             log.debug("No session associated with session ID {} - session must have timed out", valueComponents[1]);
    157         }
    158         return null;
    159     }
    160
    161     /**
    162      * Validates the session cookie. This validates that the cookie came from the same IP address to which it was given,
    163      * if consistent address checking is enabled, and that cookie data hasn't been changed.
    164      *
    165      * @param httpRequest incoming HTTP request
    166      * @param remoteAddressBytes remote address from the cookie value
    167      * @param sessionIdBytes session ID from the cookie value
    168      * @param signatureBytes signature from the cookie value
    169      * @param sessionSecret secrete associated with the user's session
    170      *
    171      * @return true if the information in the cookie is valid, false if not
    172      */
    173     protected boolean isCookieValid(HttpServletRequest httpRequest, byte[] remoteAddressBytes, byte[] sessionIdBytes,
    174             byte[] signatureBytes, byte[] sessionSecret) {
    175         if (consistentAddress) {
    176             String remoteAddress = new String(remoteAddressBytes);
    177             if (!httpRequest.getRemoteAddr().equals(remoteAddress)) {
    178                 log.error("Client sent a cookie from addres {} but the cookie was issued to address {}", httpRequest
    179                         .getRemoteAddr(), remoteAddress);
    180                 return false;
    181             }
    182         }
    183
    184         try {
    185             MessageDigest digester = MessageDigest.getInstance("SHA");
    186             digester.update(sessionSecret);
    187             digester.update(remoteAddressBytes);
    188             digester.update(sessionIdBytes);
    189             if (!Arrays.equals(digester.digest(), signatureBytes)) {
    190                 log.error("Session cookie has been tampered with, its signature no longer matches expected value");
    191                 return false;
    192             }
    193         } catch (GeneralSecurityException e) {
    194             log.error("Unable to compute signature over session cookie material", e);
    195         }
    196
    197         return true;
    198     }
    199 }