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 }