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.net.InetAddress;
21 import java.net.UnknownHostException;
22 import java.util.List;
24 import javax.security.auth.Subject;
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.ServletException;
27 import javax.servlet.http.HttpServlet;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30 import javax.servlet.http.HttpSession;
32 import org.apache.log4j.Logger;
33 import org.joda.time.DateTime;
34 import org.opensaml.xml.util.DatatypeHelper;
35 import org.opensaml.xml.util.Pair;
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 /** Serial version UID. */
52 private static final long serialVersionUID = 8494202791991613148L;
55 private static final Logger LOG = Logger.getLogger(AuthenticationEngine.class);
58 * Gets the manager used to retrieve handlers for requests.
60 * @return manager used to retrieve handlers for requests
62 public IdPProfileHandlerManager getProfileHandlerManager() {
63 return (IdPProfileHandlerManager) getServletContext().getAttribute("handlerManager");
67 * Gets the session manager to be used.
69 * @return session manager to be used
71 @SuppressWarnings("unchecked")
72 public SessionManager<Session> getSessionManager() {
73 return (SessionManager<Session>) getServletContext().getAttribute("sessionManager");
77 * Returns control back to the authentication engine.
79 * @param httpRequest current http request
80 * @param httpResponse current http response
82 * @throws ServletException thrown if unable to return to authentication engine
84 public static void returnToAuthenticationEngine(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
85 throws ServletException {
86 if (LOG.isDebugEnabled()) {
87 LOG.debug("Returning control to authentication engine");
89 HttpSession httpSession = httpRequest.getSession();
90 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
91 if (loginContext == null) {
92 LOG.error("User HttpSession did not contain a login context. Unable to return to authentication engine");
93 throw new ServletException(
94 "User HttpSession did not contain a login context. Unable to return to authentication engine");
96 forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
100 * Returns control back to the profile handler that invoked the authentication engine.
102 * @param loginContext current login context
103 * @param httpRequest current http request
104 * @param httpResponse current http response
106 public static void returnToProfileHandler(LoginContext loginContext, HttpServletRequest httpRequest,
107 HttpServletResponse httpResponse) {
108 if (LOG.isDebugEnabled()) {
109 LOG.debug("Returning control to profile handler at: " + loginContext.getProfileHandlerURL());
111 forwardRequest(loginContext.getProfileHandlerURL(), httpRequest, httpResponse);
115 * Forwards a request to the given path.
117 * @param forwardPath path to forward the request to
118 * @param httpRequest current HTTP request
119 * @param httpResponse current HTTP response
121 protected static void forwardRequest(String forwardPath, HttpServletRequest httpRequest,
122 HttpServletResponse httpResponse) {
124 RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(forwardPath);
125 dispatcher.forward(httpRequest, httpResponse);
127 } catch (IOException e) {
128 LOG.fatal("Unable to return control back to authentication engine", e);
129 } catch (ServletException e) {
130 LOG.fatal("Unable to return control back to authentication engine", e);
135 @SuppressWarnings("unchecked")
136 protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException,
138 if (LOG.isDebugEnabled()) {
139 LOG.debug("Processing incoming request");
142 if (httpResponse.isCommitted()) {
143 LOG.error("HTTP Response already committed");
146 HttpSession httpSession = httpRequest.getSession();
147 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
148 if (loginContext == null) {
149 LOG.error("Incoming request does not have attached login context");
150 throw new ServletException("Incoming request does not have attached login context");
153 if (!loginContext.getAuthenticationAttempted()) {
154 String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
155 Session shibSession = getSessionManager().getSession(shibSessionId);
157 if (shibSession != null) {
158 AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(
159 loginContext, shibSession);
160 if (authenticationMethod != null) {
161 if (LOG.isDebugEnabled()) {
162 LOG.debug("An active authentication method is applicable for relying party. "
163 + "Using authentication method " + authenticationMethod.getAuthenticationMethod()
164 + " as authentication method to relying party without re-authenticating user.");
166 authenticateUserWithActiveMethod(httpRequest, httpResponse, authenticationMethod);
170 if (LOG.isDebugEnabled()) {
171 LOG.debug("No active authentication method is applicable for relying party. "
172 + "Authenticating user with to be determined method.");
174 authenticateUserWithoutActiveMethod1(httpRequest, httpResponse);
176 if (LOG.isDebugEnabled()) {
177 LOG.debug("Request returned from authentication handler, completing authentication process.");
179 authenticateUserWithoutActiveMethod2(httpRequest, httpResponse);
186 * Completes the authentication request using an existing, active, authentication method for the current user.
188 * @param httpRequest current HTTP request
189 * @param httpResponse current HTTP response
190 * @param authenticationMethod authentication method to use to complete the request
192 protected void authenticateUserWithActiveMethod(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
193 AuthenticationMethodInformation authenticationMethod) {
194 HttpSession httpSession = httpRequest.getSession();
196 String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
197 Session shibSession = getSessionManager().getSession(shibSessionId);
199 if (LOG.isDebugEnabled()) {
200 LOG.debug("Populating login context with existing session and authentication method information.");
202 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
203 loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
204 loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
205 loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
206 loginContext.setPrincipalAuthenticated(true);
207 loginContext.setPrincipalName(shibSession.getPrincipalName());
209 ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
210 authenticationMethod);
211 shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
213 returnToProfileHandler(loginContext, httpRequest, httpResponse);
217 * Performs the first part of user authentication. An authentication handler is determined, the login context is
218 * populated with some initial information, and control is forward to the selected handler so that it may
219 * authenticate the user.
221 * @param httpRequest current HTTP request
222 * @param httpResponse current HTTP response
224 protected void authenticateUserWithoutActiveMethod1(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
225 HttpSession httpSession = httpRequest.getSession();
226 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
228 if (LOG.isDebugEnabled()) {
229 LOG.debug("Selecting appropriate authentication method for request.");
231 Pair<String, LoginHandler> handler = getProfileHandlerManager().getAuthenticationHandler(loginContext);
233 if (handler == null) {
234 loginContext.setPrincipalAuthenticated(false);
235 loginContext.setAuthenticationFailureMessage("No AuthenticationHandler satisfys the request from: "
236 + loginContext.getRelyingPartyId());
237 LOG.error("No AuthenticationHandler satisfies the request from relying party: "
238 + loginContext.getRelyingPartyId());
239 returnToProfileHandler(loginContext, httpRequest, httpResponse);
243 if (LOG.isDebugEnabled()) {
244 LOG.debug("Authentication method " + handler.getFirst() + " will be used to authenticate user.");
246 loginContext.setAuthenticationAttempted();
247 loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
248 loginContext.setAuthenticationMethod(handler.getFirst());
249 loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
251 if (LOG.isDebugEnabled()) {
252 LOG.debug("Transferring control to authentication handler of type: "
253 + handler.getSecond().getClass().getName());
255 handler.getSecond().login(httpRequest, httpResponse);
259 * Performs the second part of user authentication. The principal name set by the authentication handler is
260 * retrieved and pushed in to the login context, a Shibboleth session is created if needed, information indicating
261 * that the user has logged into the service is recorded and finally control is returned back to the profile
264 * @param httpRequest current HTTP request
265 * @param httpResponse current HTTP response
267 protected void authenticateUserWithoutActiveMethod2(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
268 HttpSession httpSession = httpRequest.getSession();
270 String principalName = (String) httpRequest.getAttribute(LoginHandler.PRINCIPAL_NAME_KEY);
271 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
272 if (DatatypeHelper.isEmpty(principalName)) {
273 loginContext.setPrincipalAuthenticated(false);
274 loginContext.setAuthenticationFailureMessage("No principal name returned from authentication handler.");
275 LOG.error("No principal name returned from authentication method: "
276 + loginContext.getAuthenticationMethod());
277 returnToProfileHandler(loginContext, httpRequest, httpResponse);
280 loginContext.setPrincipalName(principalName);
281 loginContext.setAuthenticationInstant(new DateTime());
283 String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
284 Session shibSession = getSessionManager().getSession(shibSessionId);
286 if (shibSession == null) {
287 if (LOG.isDebugEnabled()) {
288 LOG.debug("Creating shibboleth session for principal " + principalName);
293 addr = InetAddress.getByName(httpRequest.getRemoteAddr());
294 } catch (UnknownHostException ex) {
298 shibSession = (Session) getSessionManager().createSession(addr, loginContext.getPrincipalName());
299 loginContext.setSessionID(shibSession.getSessionID());
300 httpSession.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, shibSession.getSessionID());
303 if (LOG.isDebugEnabled()) {
304 LOG.debug("Recording authentication and service information in Shibboleth session for principal: "
307 Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
308 AuthenticationMethodInformation authnMethodInfo = new AuthenticationMethodInformationImpl(subject, loginContext
309 .getAuthenticationMethod(), new DateTime(), loginContext.getAuthenticationDuration());
311 shibSession.getAuthenticationMethods().put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo);
313 ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
315 shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
317 shibSession.setLastActivityInstant(new DateTime());
319 returnToProfileHandler(loginContext, httpRequest, httpResponse);
323 * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
324 * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
325 * user must be authenticated.
327 * @param loginContext user login context
328 * @param shibSession user's shibboleth session
330 * @return active authentication method that meets authentication requirements or null
332 protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
333 Session shibSession) {
334 if (loginContext.getForceAuth() || shibSession == null) {
338 List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
340 if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
341 for (AuthenticationMethodInformation authnMethod : shibSession.getAuthenticationMethods().values()) {
342 if (!authnMethod.isExpired()) {
347 for (String preferredAuthnMethod : preferredAuthnMethods) {
348 if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
349 AuthenticationMethodInformation authnMethodInfo = shibSession.getAuthenticationMethods().get(
350 preferredAuthnMethod);
351 if (!authnMethodInfo.isExpired()) {
352 return authnMethodInfo;