View Javadoc

    1   /*
    2    * Copyright 2006 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.authn.provider;
    18
    19  import java.io.IOException;
    20  import java.security.Principal;
    21  import java.util.Set;
    22
    23  import javax.security.auth.Subject;
    24  import javax.security.auth.callback.Callback;
    25  import javax.security.auth.callback.CallbackHandler;
    26  import javax.security.auth.callback.NameCallback;
    27  import javax.security.auth.callback.PasswordCallback;
    28  import javax.security.auth.callback.UnsupportedCallbackException;
    29  import javax.security.auth.login.LoginException;
    30  import javax.servlet.ServletConfig;
    31  import javax.servlet.ServletException;
    32  import javax.servlet.http.HttpServlet;
    33  import javax.servlet.http.HttpServletRequest;
    34  import javax.servlet.http.HttpServletResponse;
    35
    36  import org.opensaml.xml.util.DatatypeHelper;
    37  import org.slf4j.Logger;
    38  import org.slf4j.LoggerFactory;
    39
    40  import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
    41  import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationException;
    42  import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
    43  import edu.internet2.middleware.shibboleth.idp.authn.UsernamePrincipal;
    44
    45  /**
    46   * This Servlet authenticates a user via JAAS. The user's credential is always added to the returned {@link Subject} as
    47   * a {@link UsernamePasswordCredential} within the subject's private credentials.
    48   */
    49  public class UsernamePasswordLoginServlet extends HttpServlet {
    50
    51      /** Serial version UID. */
    52      private static final long serialVersionUID = -572799841125956990L;
    53
    54      /** Class logger. */
    55      private final Logger log = LoggerFactory.getLogger(UsernamePasswordLoginServlet.class);
    56
    57      /** Name of JAAS configuration used to authenticate users. */
    58      private String jaasConfigName = "ShibUserPassAuth";
    59
    60      /** init-param which can be passed to the servlet to override the default JAAS config. */
    61      private final String jaasInitParam = "jaasConfigName";
    62
    63      /** Login page name. */
    64      private String loginPage = "login.jsp";
    65
    66      /** init-param which can be passed to the servlet to override the default login page. */
    67      private final String loginPageInitParam = "loginPage";
    68
    69      /** Parameter name to indicate login failure. */
    70      private final String failureParam = "loginFailed";
    71
    72      /** HTTP request parameter containing the user name. */
    73      private final String usernameAttribute = "j_username";
    74
    75      /** HTTP request parameter containing the user's password. */
    76      private final String passwordAttribute = "j_password";
    77
    78      /** {@inheritDoc} */
    79      public void init(ServletConfig config) throws ServletException {
    80          super.init(config);
    81
    82          if (getInitParameter(jaasInitParam) != null) {
    83              jaasConfigName = getInitParameter(jaasInitParam);
    84          }
    85
    86          if (getInitParameter(loginPageInitParam) != null) {
    87              loginPage = getInitParameter(loginPageInitParam);
    88          }
    89          if (!loginPage.startsWith("/")) {
    90              loginPage = "/" + loginPage;
    91          }
    92      }
    93
    94      /** {@inheritDoc} */
    95      protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
    96              IOException {
    97          String username = request.getParameter(usernameAttribute);
    98          String password = request.getParameter(passwordAttribute);
    99
    100         if (username == null || password == null) {
    101             redirectToLoginPage(request, response);
    102             return;
    103         }
    104
    105         try {
    106             authenticateUser(request, username, password);
    107             AuthenticationEngine.returnToAuthenticationEngine(request, response);
    108         } catch (LoginException e) {
    109             request.setAttribute(failureParam, "true");
    110             request.setAttribute(LoginHandler.AUTHENTICATION_EXCEPTION_KEY, new AuthenticationException(e));
    111             redirectToLoginPage(request, response);
    112         }
    113     }
    114
    115     /**
    116      * Sends the user to the login page.
    117      *
    118      * @param request current request
    119      * @param response current response
    120      */
    121     protected void redirectToLoginPage(HttpServletRequest request, HttpServletResponse response) {
    122
    123         StringBuilder actionUrlBuilder = new StringBuilder();
    124         if(!"".equals(request.getContextPath())){
    125             actionUrlBuilder.append(request.getContextPath());
    126         }
    127         actionUrlBuilder.append(request.getServletPath());
    128
    129         request.setAttribute("actionUrl", actionUrlBuilder.toString());
    130
    131         try {
    132             request.getRequestDispatcher(loginPage).forward(request, response);
    133             log.debug("Redirecting to login page {}", loginPage);
    134         } catch (IOException ex) {
    135             log.error("Unable to redirect to login page.", ex);
    136         } catch (ServletException ex) {
    137             log.error("Unable to redirect to login page.", ex);
    138         }
    139     }
    140
    141     /**
    142      * Authenticate a username and password against JAAS. If authentication succeeds the name of the first principal, or
    143      * the username if that is empty, and the subject are placed into the request in their respective attributes.
    144      *
    145      * @param request current authentication request
    146      * @param username the principal name of the user to be authenticated
    147      * @param password the password of the user to be authenticated
    148      *
    149      * @throws LoginException thrown if there is a problem authenticating the user
    150      */
    151     protected void authenticateUser(HttpServletRequest request, String username, String password) throws LoginException {
    152         try {
    153             log.debug("Attempting to authenticate user {}", username);
    154
    155             SimpleCallbackHandler cbh = new SimpleCallbackHandler(username, password);
    156
    157             javax.security.auth.login.LoginContext jaasLoginCtx = new javax.security.auth.login.LoginContext(
    158                     jaasConfigName, cbh);
    159
    160             jaasLoginCtx.login();
    161             log.debug("Successfully authenticated user {}", username);
    162
    163             Subject loginSubject = jaasLoginCtx.getSubject();
    164
    165             Set<Principal> principals = loginSubject.getPrincipals();
    166             principals.add(new UsernamePrincipal(username));
    167
    168             Set<Object> publicCredentials = loginSubject.getPublicCredentials();
    169
    170             Set<Object> privateCredentials = loginSubject.getPrivateCredentials();
    171             privateCredentials.add(new UsernamePasswordCredential(username, password));
    172
    173             Subject userSubject = new Subject(false, principals, publicCredentials, privateCredentials);
    174             request.setAttribute(LoginHandler.SUBJECT_KEY, userSubject);
    175         } catch (LoginException e) {
    176             log.debug("User authentication for " + username + " failed", e);
    177             throw e;
    178         } catch (Throwable e) {
    179             log.debug("User authentication for " + username + " failed", e);
    180             throw new LoginException("unknown authentication error");
    181         }
    182     }
    183
    184     /**
    185      * A callback handler that provides static name and password data to a JAAS loging process.
    186      *
    187      * This handler only supports {@link NameCallback} and {@link PasswordCallback}.
    188      */
    189     protected class SimpleCallbackHandler implements CallbackHandler {
    190
    191         /** Name of the user. */
    192         private String uname;
    193
    194         /** User's password. */
    195         private String pass;
    196
    197         /**
    198          * Constructor.
    199          *
    200          * @param username The username
    201          * @param password The password
    202          */
    203         public SimpleCallbackHandler(String username, String password) {
    204             uname = username;
    205             pass = password;
    206         }
    207
    208         /**
    209          * Handle a callback.
    210          *
    211          * @param callbacks The list of callbacks to process.
    212          *
    213          * @throws UnsupportedCallbackException If callbacks has a callback other than {@link NameCallback} or
    214          *             {@link PasswordCallback}.
    215          */
    216         public void handle(final Callback[] callbacks) throws UnsupportedCallbackException {
    217
    218             if (callbacks == null || callbacks.length == 0) {
    219                 return;
    220             }
    221
    222             for (Callback cb : callbacks) {
    223                 if (cb instanceof NameCallback) {
    224                     NameCallback ncb = (NameCallback) cb;
    225                     ncb.setName(uname);
    226                 } else if (cb instanceof PasswordCallback) {
    227                     PasswordCallback pcb = (PasswordCallback) cb;
    228                     pcb.setPassword(pass.toCharArray());
    229                 }
    230             }
    231         }
    232     }
    233 }