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          MDC.put("JSESSIONID", httpRequest.getSession().getId());
69          MDC.put("clientIP", httpRequest.getRemoteAddr());
70  
71          Cookie sessionCookie = getIdPSessionCookie(httpRequest);
72          Session idpSession = getUserSession(sessionCookie, httpRequest);
73          if (idpSession != null) {
74              log.trace("Updating IdP session activity time and adding session object to the request");
75              idpSession.setLastActivityInstant(new DateTime());
76              MDC.put("idpSessionId", idpSession.getSessionID());
77              httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, idpSession);
78          }
79  
80          filterChain.doFilter(request, response);
81      }
82  
83      /** {@inheritDoc} */
84      public void init(FilterConfig filterConfig) throws ServletException {
85          String sessionManagerId = filterConfig.getInitParameter("sessionManagedId");
86          if (DatatypeHelper.isEmpty(sessionManagerId)) {
87              sessionManagerId = "shibboleth.SessionManager";
88          }
89  
90          sessionManager = (SessionManager<Session>) filterConfig.getServletContext().getAttribute(sessionManagerId);
91  
92          String consistentAddressParam = filterConfig.getInitParameter("ensureConsistentClientAddress");
93          if (DatatypeHelper.isEmpty(consistentAddressParam)) {
94              consistentAddress = true;
95          } else {
96              consistentAddress = Boolean.parseBoolean(consistentAddressParam);
97          }
98      }
99  
100     /**
101      * Gets the IdP session cookie from the current request, if the user currently has a session.
102      * 
103      * @param httpRequest current HTTP request
104      * 
105      * @return the user's current IdP session cookie, if they have a current session, otherwise null
106      */
107     protected Cookie getIdPSessionCookie(HttpServletRequest httpRequest) {
108         log.trace("Attempting to retrieve IdP session cookie.");
109         Cookie[] requestCookies = httpRequest.getCookies();
110 
111         if (requestCookies != null) {
112             for (Cookie requestCookie : requestCookies) {
113                 if (DatatypeHelper.safeEquals(requestCookie.getName(), AuthenticationEngine.IDP_SESSION_COOKIE_NAME)) {
114                     log.trace("Found IdP session cookie.");
115                     return requestCookie;
116                 }
117             }
118         }
119 
120         return null;
121     }
122 
123     /**
124      * Gets the user session associated with a session cookie.
125      * 
126      * @param sessionCookie the session cookie
127      * @param httpRequest the current HTTP request
128      * 
129      * @return the session associated with the cookie or null if there is no currently assoicated session
130      */
131     protected Session getUserSession(Cookie sessionCookie, HttpServletRequest httpRequest) {
132         if (sessionCookie == null || DatatypeHelper.isEmpty(sessionCookie.getValue())) {
133             return null;
134         }
135 
136         // index 0: remote address
137         // index 1: session ID
138         // index 2: Base64(HMAC(index 0 + index 1))
139         String cookieValue = HTTPTransportUtils.urlDecode(sessionCookie.getValue());
140         String[] valueComponents = cookieValue.split("\\|");
141         if (valueComponents.length != 3) {
142             log.warn("IdP session cookie has an improperly formated value: {}", cookieValue);
143             return null;
144         }
145 
146         byte[] remoteAddressBytes = Base64.decode(valueComponents[0]);
147         byte[] sessionIdBytes = Base64.decode(valueComponents[1]);
148         byte[] signatureBytes = Base64.decode(valueComponents[2]);
149 
150         String sessionId = new String(sessionIdBytes);
151         Session userSession = sessionManager.getSession(sessionId);
152 
153         if (userSession != null) {
154             if (isCookieValid(httpRequest, remoteAddressBytes, sessionIdBytes, signatureBytes, userSession
155                     .getSessionSecret())) {
156                 return userSession;
157             }
158         } else {
159             log.debug("No session associated with session ID {} - session must have timed out", valueComponents[1]);
160         }
161         return null;
162     }
163 
164     /**
165      * Validates the session cookie. This validates that the cookie came from the same IP address to which it was given,
166      * if consistent address checking is enabled, and that cookie data hasn't been changed.
167      * 
168      * @param httpRequest incoming HTTP request
169      * @param remoteAddressBytes remote address from the cookie value
170      * @param sessionIdBytes session ID from the cookie value
171      * @param signatureBytes signature from the cookie value
172      * @param sessionSecret secrete associated with the user's session
173      * 
174      * @return true if the information in the cookie is valid, false if not
175      */
176     protected boolean isCookieValid(HttpServletRequest httpRequest, byte[] remoteAddressBytes, byte[] sessionIdBytes,
177             byte[] signatureBytes, byte[] sessionSecret) {
178         if (consistentAddress) {
179             String remoteAddress = new String(remoteAddressBytes);
180             if (!httpRequest.getRemoteAddr().equals(remoteAddress)) {
181                 log.error("Client sent a cookie from address {} but the cookie was issued to address {}", httpRequest
182                         .getRemoteAddr(), remoteAddress);
183                 return false;
184             }
185         }
186 
187         try {
188             MessageDigest digester = MessageDigest.getInstance("SHA");
189             digester.update(sessionSecret);
190             digester.update(remoteAddressBytes);
191             digester.update(sessionIdBytes);
192             if (!Arrays.equals(digester.digest(), signatureBytes)) {
193                 log.error("Session cookie has been tampered with, its signature no longer matches expected value");
194                 return false;
195             }
196         } catch (GeneralSecurityException e) {
197             log.error("Unable to compute signature over session cookie material", e);
198         }
199 
200         return true;
201     }
202 }