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