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.servlet.RequestDispatcher;
25 import javax.servlet.ServletException;
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.apache.log4j.Logger;
32 import org.joda.time.DateTime;
33 import org.opensaml.xml.util.DatatypeHelper;
34 import org.opensaml.xml.util.Pair;
36 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandlerManager;
37 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
38 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
39 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
40 import edu.internet2.middleware.shibboleth.idp.session.Session;
41 import edu.internet2.middleware.shibboleth.idp.session.impl.AuthenticationMethodInformationImpl;
42 import edu.internet2.middleware.shibboleth.idp.session.impl.ServiceInformationImpl;
45 * Manager responsible for handling authentication requests.
47 public class AuthenticationEngine extends HttpServlet {
50 private static final Logger LOG = Logger.getLogger(AuthenticationEngine.class);
53 * Gets the manager used to retrieve handlers for requests.
55 * @return manager used to retrieve handlers for requests
57 public ProfileHandlerManager getProfileHandlerManager() {
58 return (ProfileHandlerManager) getServletContext().getAttribute("handlerManager");
62 * Gets the session manager to be used.
64 * @return session manager to be used
66 @SuppressWarnings("unchecked")
67 public SessionManager<Session> getSessionManager() {
68 return (SessionManager<Session>) getServletContext().getAttribute("sessionManager");
72 * Gets the authentication handler manager used by this engine.
74 * @return authentication handler manager used by this engine
76 public AuthenticationHandlerManager getAuthenticationHandlerManager() {
77 return (AuthenticationHandlerManager) getServletContext().getAttribute("authenticationHandlerManager");
81 * Returns control back to the authentication engine.
83 * @param httpRequest current http request
84 * @param httpResponse current http response
86 public static void returnToAuthenticationEngine(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
87 if (LOG.isDebugEnabled()) {
88 LOG.debug("Returning control to authentication engine");
90 HttpSession httpSession = httpRequest.getSession();
91 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
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 if (LOG.isDebugEnabled()) {
105 LOG.debug("Returning control to profile handler at: " + loginContext.getProfileHandlerURL());
107 forwardRequest(loginContext.getProfileHandlerURL(), httpRequest, httpResponse);
111 * Forwards a request to the given path.
113 * @param forwardPath path to forward the request to
114 * @param httpRequest current HTTP request
115 * @param httpResponse current HTTP response
117 protected static void forwardRequest(String forwardPath, HttpServletRequest httpRequest,
118 HttpServletResponse httpResponse) {
120 RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(forwardPath);
121 dispatcher.forward(httpRequest, httpResponse);
122 } catch (IOException e) {
123 LOG.fatal("Unable to return control back to authentication engine", e);
124 } catch (ServletException e) {
125 LOG.fatal("Unable to return control back to authentication engine", e);
130 @SuppressWarnings("unchecked")
131 protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException,
133 if (LOG.isDebugEnabled()) {
134 LOG.debug("Processing incoming request");
137 HttpSession httpSession = httpRequest.getSession();
138 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
139 if (loginContext == null) {
140 LOG.error("Incoming request does not have attached login context");
141 throw new ServletException("Incoming request does not have attached login context");
144 if (!loginContext.getAuthenticationAttempted()) {
145 String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
146 Session shibSession = getSessionManager().getSession(shibSessionId);
148 if (shibSession != null) {
149 AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(
150 loginContext, shibSession);
151 if (authenticationMethod != null) {
152 if (LOG.isDebugEnabled()) {
153 LOG.debug("An active authentication method is applicable for relying party. "
154 + "Using authentication method " + authenticationMethod.getAuthenticationMethod()
155 + " as authentication method to relying party without re-authenticating user.");
157 authenticateUserWithActiveMethod(httpRequest, httpResponse, authenticationMethod);
161 if (LOG.isDebugEnabled()) {
162 LOG.debug("No active authentication method is applicable for relying party. "
163 + "Authenticating user with to be determined method.");
165 authenticateUserWithoutActiveMethod1(httpRequest, httpResponse);
167 if (LOG.isDebugEnabled()) {
168 LOG.debug("Request returned from authentication handler, completing authentication process.");
170 authenticateUserWithoutActiveMethod2(httpRequest, httpResponse);
175 * Completes the authentication request using an existing, active, authentication method for the current user.
177 * @param httpRequest current HTTP request
178 * @param httpResponse current HTTP response
179 * @param authenticationMethod authentication method to use to complete the request
181 protected void authenticateUserWithActiveMethod(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
182 AuthenticationMethodInformation authenticationMethod) {
183 HttpSession httpSession = httpRequest.getSession();
185 String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
186 Session shibSession = getSessionManager().getSession(shibSessionId);
188 if (LOG.isDebugEnabled()) {
189 LOG.debug("Populating login context with existing session and authentication method information.");
191 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
192 loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
193 loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
194 loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
195 loginContext.setPrincipalAuthenticated(true);
196 loginContext.setPrincipalName(shibSession.getPrincipalName());
198 ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
199 authenticationMethod);
200 shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
202 returnToProfileHandler(loginContext, httpRequest, httpResponse);
206 * Performs the first part of user authentication. An authentication handler is determined, the login context is
207 * populated with some initial information, and control is forward to the selected handler so that it may
208 * authenticate the user.
210 * @param httpRequest current HTTP request
211 * @param httpResponse current HTTP response
213 protected void authenticateUserWithoutActiveMethod1(HttpServletRequest httpRequest,
214 HttpServletResponse httpResponse) {
215 HttpSession httpSession = httpRequest.getSession();
216 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
218 if (LOG.isDebugEnabled()) {
219 LOG.debug("Selecting appropriate authentication method for request.");
221 Pair<String, AuthenticationHandler> handler = getAuthenticationHandlerManager().getAuthenticationHandler(
224 if (handler == null) {
225 loginContext.setPrincipalAuthenticated(false);
226 loginContext.setAuthenticationFailureMessage("No AuthenticationHandler satisfys the request from: "
227 + loginContext.getRelyingPartyId());
228 LOG.error("No AuthenticationHandler satisfys the request from relying party: "
229 + loginContext.getRelyingPartyId());
230 returnToProfileHandler(loginContext, httpRequest, httpResponse);
233 if (LOG.isDebugEnabled()) {
234 LOG.debug("Authentication method " + handler.getFirst() + " will be used to authenticate user.");
236 loginContext.setAuthenticationAttempted();
237 loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
238 loginContext.setAuthenticationMethod(handler.getFirst());
239 loginContext.setAuthenticationEngineURL(httpRequest.getRequestURI());
241 if (LOG.isDebugEnabled()) {
242 LOG.debug("Transferring control to authentication handler of type: "
243 + handler.getSecond().getClass().getName());
245 handler.getSecond().login(httpRequest, httpResponse);
249 * Performs the second part of user authentication. The principal name set by the authentication handler is
250 * retrieved and pushed in to the login context, a Shibboleth session is created if needed, information indicating
251 * that the user has logged into the service is recorded and finally control is returned back to the profile
254 * @param httpRequest current HTTP request
255 * @param httpResponse current HTTP response
257 protected void authenticateUserWithoutActiveMethod2(HttpServletRequest httpRequest,
258 HttpServletResponse httpResponse) {
259 HttpSession httpSession = httpRequest.getSession();
261 String principalName = (String) httpRequest.getAttribute(AuthenticationHandler.PRINCIPAL_NAME_KEY);
262 LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
263 if (DatatypeHelper.isEmpty(principalName)) {
264 loginContext.setPrincipalAuthenticated(false);
265 loginContext.setAuthenticationFailureMessage("No principal name returned from authentication handler.");
266 LOG.error("No principal name returned from authentication method: "
267 + loginContext.getAuthenticationMethod());
268 returnToProfileHandler(loginContext, httpRequest, httpResponse);
270 loginContext.setPrincipalName(principalName);
272 String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
273 Session shibSession = getSessionManager().getSession(shibSessionId);
275 if (shibSession == null) {
276 if (LOG.isDebugEnabled()) {
277 LOG.debug("Creating shibboleth session for principal " + principalName);
282 addr = InetAddress.getByName(httpRequest.getRemoteAddr());
283 } catch (UnknownHostException ex) {
287 shibSession = (Session) getSessionManager().createSession(addr, loginContext.getPrincipalName());
288 loginContext.setSessionID(shibSession.getSessionID());
289 httpSession.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, shibSession.getSessionID());
292 if (LOG.isDebugEnabled()) {
293 LOG.debug("Recording authentication and service information in Shibboleth session for principal: "
296 AuthenticationMethodInformation authnMethodInfo = new AuthenticationMethodInformationImpl(loginContext
297 .getAuthenticationMethod(), new DateTime(), loginContext.getAuthenticationDuration());
298 shibSession.getAuthenticationMethods().put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo);
300 ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
302 shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
304 shibSession.setLastActivityInstant(new DateTime());
306 returnToProfileHandler(loginContext, httpRequest, httpResponse);
310 * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
311 * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
312 * user must be authenticated.
314 * @param loginContext user login context
315 * @param shibSession user's shibboleth session
317 * @return active authentication method that meets authentication requirements or null
319 protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
320 Session shibSession) {
321 if (loginContext.getForceAuth() || shibSession == null) {
325 List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
327 if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
328 for (AuthenticationMethodInformation authnMethod : shibSession.getAuthenticationMethods().values()) {
329 if (!authnMethod.isExpired()) {
334 for (String preferredAuthnMethod : preferredAuthnMethods) {
335 if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
336 AuthenticationMethodInformation authnMethodInfo = shibSession.getAuthenticationMethods().get(
337 preferredAuthnMethod);
338 if (!authnMethodInfo.isExpired()) {
339 return authnMethodInfo;