2 * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.idp.authn;
19 import java.io.IOException;
20 import java.util.List;
22 import javax.security.auth.Subject;
23 import javax.servlet.RequestDispatcher;
24 import javax.servlet.ServletException;
25 import javax.servlet.http.Cookie;
26 import javax.servlet.http.HttpServlet;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29 import javax.servlet.http.HttpSession;
31 import org.joda.time.DateTime;
32 import org.opensaml.xml.util.DatatypeHelper;
33 import org.opensaml.xml.util.Pair;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
37 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
38 import edu.internet2.middleware.shibboleth.common.util.HttpHelper;
39 import edu.internet2.middleware.shibboleth.idp.profile.IdPProfileHandlerManager;
40 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
41 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
42 import edu.internet2.middleware.shibboleth.idp.session.Session;
43 import edu.internet2.middleware.shibboleth.idp.session.impl.AuthenticationMethodInformationImpl;
44 import edu.internet2.middleware.shibboleth.idp.session.impl.ServiceInformationImpl;
47 * Manager responsible for handling authentication requests.
49 public class AuthenticationEngine extends HttpServlet {
51 /** Name of the IdP Cookie containing the IdP session ID. */
52 public static final String IDP_SESSION_COOKIE_NAME = "_idp_session";
54 /** Serial version UID. */
55 private static final long serialVersionUID = 8494202791991613148L;
58 private static final Logger LOG = LoggerFactory.getLogger(AuthenticationEngine.class);
61 * Gets the manager used to retrieve handlers for requests.
63 * @return manager used to retrieve handlers for requests
65 public IdPProfileHandlerManager getProfileHandlerManager() {
66 return (IdPProfileHandlerManager) getServletContext().getAttribute("handlerManager");
70 * Gets the session manager to be used.
72 * @return session manager to be used
74 @SuppressWarnings("unchecked")
75 public SessionManager<Session> getSessionManager() {
76 return (SessionManager<Session>) getServletContext().getAttribute("sessionManager");
80 * Returns control back to the authentication engine.
82 * @param httpRequest current http request
83 * @param httpResponse current http response
85 public static void returnToAuthenticationEngine(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
86 LOG.debug("Returning control to authentication engine");
87 HttpSession httpSession = httpRequest.getSession();
88 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
89 if (loginContext == null) {
90 LOG.error("User HttpSession did not contain a login context. Unable to return to authentication engine");
92 forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
96 * Returns control back to the profile handler that invoked the authentication engine.
98 * @param loginContext current login context
99 * @param httpRequest current http request
100 * @param httpResponse current http response
102 public static void returnToProfileHandler(LoginContext loginContext, HttpServletRequest httpRequest,
103 HttpServletResponse httpResponse) {
104 LOG.debug("Returning control to profile handler at: {}", loginContext.getProfileHandlerURL());
105 forwardRequest(loginContext.getProfileHandlerURL(), httpRequest, httpResponse);
109 * Forwards a request to the given path.
111 * @param forwardPath path to forward the request to
112 * @param httpRequest current HTTP request
113 * @param httpResponse current HTTP response
115 protected static void forwardRequest(String forwardPath, HttpServletRequest httpRequest,
116 HttpServletResponse httpResponse) {
118 RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(forwardPath);
119 dispatcher.forward(httpRequest, httpResponse);
121 } catch (IOException e) {
122 LOG.error("Unable to return control back to authentication engine", e);
123 } catch (ServletException e) {
124 LOG.error("Unable to return control back to authentication engine", e);
129 @SuppressWarnings("unchecked")
130 protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException,
132 LOG.debug("Processing incoming request");
134 if (httpResponse.isCommitted()) {
135 LOG.error("HTTP Response already committed");
138 HttpSession httpSession = httpRequest.getSession();
139 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
140 if (loginContext == null) {
141 LOG.error("Incoming request does not have attached login context");
142 throw new ServletException("Incoming request does not have attached login context");
145 if (!loginContext.getAuthenticationAttempted()) {
146 Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
148 AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(loginContext,
150 if (authenticationMethod != null) {
151 LOG.debug("An active authentication method is applicable for relying party. Using authentication "
152 + "method {} as authentication method to relying party without re-authenticating user.",
153 authenticationMethod.getAuthenticationMethod());
154 authenticateUserWithActiveMethod(httpRequest, httpResponse, authenticationMethod);
158 LOG.debug("No active authentication method is applicable for relying party. "
159 + "Authenticating user with to be determined method.");
160 authenticateUserWithoutActiveMethod1(httpRequest, httpResponse);
162 LOG.debug("Request returned from authentication handler, completing authentication process.");
163 authenticateUserWithoutActiveMethod2(httpRequest, httpResponse);
170 * Completes the authentication request using an existing, active, authentication method for the current user.
172 * @param httpRequest current HTTP request
173 * @param httpResponse current HTTP response
174 * @param authenticationMethod authentication method to use to complete the request
176 protected void authenticateUserWithActiveMethod(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
177 AuthenticationMethodInformation authenticationMethod) {
178 HttpSession httpSession = httpRequest.getSession();
180 Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
182 LOG.debug("Populating login context with existing session and authentication method information.");
183 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
184 loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
185 loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
186 loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
187 loginContext.setPrincipalAuthenticated(true);
188 loginContext.setPrincipalName(shibSession.getPrincipalName());
190 ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
191 authenticationMethod);
192 shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
194 returnToProfileHandler(loginContext, httpRequest, httpResponse);
198 * Performs the first part of user authentication. An authentication handler is determined, the login context is
199 * populated with some initial information, and control is forward to the selected handler so that it may
200 * authenticate the user.
202 * @param httpRequest current HTTP request
203 * @param httpResponse current HTTP response
205 protected void authenticateUserWithoutActiveMethod1(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
206 HttpSession httpSession = httpRequest.getSession();
207 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
208 LOG.debug("Selecting appropriate authentication method for request.");
209 Pair<String, LoginHandler> handler = getProfileHandlerManager().getAuthenticationHandler(loginContext);
211 if (handler == null) {
212 loginContext.setPrincipalAuthenticated(false);
213 loginContext.setAuthenticationAttempted();
214 loginContext.setAuthenticationFailureMessage("No AuthenticationHandler satisfies the request from: "
215 + loginContext.getRelyingPartyId());
216 LOG.error("No AuthenticationHandler satisfies the request from relying party: "
217 + loginContext.getRelyingPartyId());
218 returnToProfileHandler(loginContext, httpRequest, httpResponse);
222 LOG.debug("Authentication method {} will be used to authenticate user.", handler.getFirst());
223 loginContext.setAuthenticationAttempted();
224 loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
225 loginContext.setAuthenticationMethod(handler.getFirst());
226 loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
228 LOG.debug("Transferring control to authentication handler of type: {}", handler.getSecond().getClass()
230 handler.getSecond().login(httpRequest, httpResponse);
234 * Performs the second part of user authentication. The principal name set by the authentication handler is
235 * retrieved and pushed in to the login context, a Shibboleth session is created if needed, information indicating
236 * that the user has logged into the service is recorded and finally control is returned back to the profile
239 * @param httpRequest current HTTP request
240 * @param httpResponse current HTTP response
242 protected void authenticateUserWithoutActiveMethod2(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
243 HttpSession httpSession = httpRequest.getSession();
245 String principalName = (String) httpRequest.getAttribute(LoginHandler.PRINCIPAL_NAME_KEY);
246 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
247 if (DatatypeHelper.isEmpty(principalName)) {
248 loginContext.setPrincipalAuthenticated(false);
249 loginContext.setAuthenticationFailureMessage("No principal name returned from authentication handler.");
250 LOG.error("No principal name returned from authentication method: "
251 + loginContext.getAuthenticationMethod());
252 returnToProfileHandler(loginContext, httpRequest, httpResponse);
255 loginContext.setPrincipalAuthenticated(true);
256 loginContext.setPrincipalName(principalName);
257 loginContext.setAuthenticationInstant(new DateTime());
259 Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
260 if (shibSession == null) {
261 LOG.debug("Creating shibboleth session for principal {}", principalName);
262 shibSession = (Session) getSessionManager().createSession(loginContext.getPrincipalName());
263 loginContext.setSessionID(shibSession.getSessionID());
264 addSessionCookie(httpRequest, httpResponse, shibSession);
267 LOG.debug("Recording authentication and service information in Shibboleth session for principal: {}",
269 Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
270 String authnMethod = (String) httpRequest.getAttribute(LoginHandler.AUTHENTICATION_METHOD_KEY);
271 if (DatatypeHelper.isEmpty(authnMethod)) {
272 authnMethod = loginContext.getAuthenticationMethod();
275 AuthenticationMethodInformation authnMethodInfo = new AuthenticationMethodInformationImpl(subject, authnMethod,
276 new DateTime(), loginContext.getAuthenticationDuration());
278 shibSession.getAuthenticationMethods().put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo);
280 ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
282 shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
284 returnToProfileHandler(loginContext, httpRequest, httpResponse);
288 * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
289 * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
290 * user must be authenticated.
292 * @param loginContext user login context
293 * @param shibSession user's shibboleth session
295 * @return active authentication method that meets authentication requirements or null
297 protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
298 Session shibSession) {
300 if (shibSession == null) {
301 LOG.debug("No existing authentication methods due to the lack of existing IdP sessions");
305 if (loginContext.getForceAuth()) {
306 LOG.debug("Request for forced re-authentication, no existing authentication method considered usable");
310 List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
311 AuthenticationMethodInformation authnMethodInformation = null;
312 if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
313 for (AuthenticationMethodInformation info : shibSession.getAuthenticationMethods().values()) {
314 if (!info.isExpired()) {
315 authnMethodInformation = info;
320 for (String preferredAuthnMethod : preferredAuthnMethods) {
321 if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
322 AuthenticationMethodInformation info = shibSession.getAuthenticationMethods().get(
323 preferredAuthnMethod);
324 if (!info.isExpired()) {
325 authnMethodInformation = info;
332 return authnMethodInformation;
336 * Adds an IdP session cookie to the outbound response.
338 * @param httpRequest current request
339 * @param httpResponse current response
340 * @param userSession user's session
342 protected void addSessionCookie(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
343 Session userSession) {
344 httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, userSession);
346 LOG.debug("Adding IdP session cookie to HTTP response");
347 Cookie sessionCookie = new Cookie(IDP_SESSION_COOKIE_NAME, userSession.getSessionID());
348 sessionCookie.setPath(httpRequest.getContextPath());
349 sessionCookie.setSecure(false);
351 int maxAge = (int) (userSession.getInactivityTimeout() / 1000);
352 sessionCookie.setMaxAge(maxAge);
354 httpResponse.addCookie(sessionCookie);