View Javadoc

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