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 }