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 }