2991059f5cfca0a0f6cb22568edf96369098aaef
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / authn / provider / UsernamePasswordLoginServlet.java
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
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.ServletException;
32 import javax.servlet.http.HttpServlet;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35
36 import org.apache.log4j.Logger;
37 import org.opensaml.util.URLBuilder;
38 import org.opensaml.xml.util.DatatypeHelper;
39 import org.opensaml.xml.util.Pair;
40
41 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
42 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
43
44 /**
45  * This servlet should be protected by a filter which populates REMOTE_USER. The serlvet will then set the remote user
46  * field in a LoginContext.
47  */
48 public class UsernamePasswordLoginServlet extends HttpServlet {
49
50     /** Serial version UID. */
51     private static final long serialVersionUID = -572799841125956990L;
52
53     /** Class logger. */
54     private final Logger log = Logger.getLogger(RemoteUserAuthServlet.class);
55
56     /** Name of JAAS configuration used to authenticate users. */
57     private final String jaasConfigName = "ShibUserPassAuth";
58
59     /** Login page name. */
60     private final String loginPage = "login.jsp";
61     
62     /** Parameter name to indicate login failure. */
63     private final String failureParam = "loginFailed";
64
65     /** HTTP request parameter containing the user name. */
66     private final String usernameAttribute = "j_username";
67
68     /** HTTP request parameter containing the user's password. */
69     private final String passwordAttribute = "j_password";
70
71     /** {@inheritDoc} */
72     protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
73             IOException {
74         String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
75         String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
76
77         if (username == null || password == null) {
78             redirectToLoginPage(request, response, null);
79             return;
80         }
81
82         if (authenticateUser(request)) {
83             try {
84                 AuthenticationEngine.returnToAuthenticationEngine(request, response);
85             } catch (ServletException e) {
86                 throw new ServletException("Unable to return to authentication engine.  "
87                         + "Authentication servlet should not be accessed directly.");
88             }
89         } else {
90             List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
91             queryParams.add(new Pair<String, String>(failureParam, "true"));
92             redirectToLoginPage(request, response, queryParams);
93             return;
94         }
95     }
96
97     /**
98      * Sends the user to the login page.
99      * 
100      * @param request current request
101      * @param response current response
102      * @param queryParams query parameters to pass to the login page
103      */
104     protected void redirectToLoginPage(HttpServletRequest request, HttpServletResponse response,
105             List<Pair<String, String>> queryParams) {
106         try {
107             StringBuilder pathBuilder = new StringBuilder();
108             pathBuilder.append(request.getContextPath());
109             pathBuilder.append("/");
110             pathBuilder.append(loginPage);
111
112             URLBuilder urlBuilder = new URLBuilder();
113             urlBuilder.setScheme(request.getScheme());
114             urlBuilder.setHost(request.getLocalName());
115             urlBuilder.setPort(request.getLocalPort());
116             urlBuilder.setPath(pathBuilder.toString());
117             if (queryParams != null) {
118                 urlBuilder.getQueryParams().addAll(queryParams);
119             }
120             
121             if (log.isDebugEnabled()) {
122                 log.debug("Redirecting to login page " + urlBuilder.buildURL());
123             }
124
125             response.sendRedirect(urlBuilder.buildURL());
126             return;
127         } catch (IOException ex) {
128             log.error("Unable to redirect to login page.", ex);
129         }
130     }
131
132     /**
133      * Authenticate a username and password against JAAS. If authentication succeeds the principal name and subject are
134      * placed into the request in their respective attributes.
135      * 
136      * @param request current authentication request
137      * 
138      * @return true of authentication succeeds, false if not
139      */
140     protected boolean authenticateUser(HttpServletRequest request) {
141
142         try {
143             String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
144             String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
145
146             SimpleCallbackHandler cbh = new SimpleCallbackHandler(username, password);
147
148             javax.security.auth.login.LoginContext jaasLoginCtx = new javax.security.auth.login.LoginContext(
149                     jaasConfigName, cbh);
150
151             jaasLoginCtx.login();
152             log.debug("Successfully authenticated user " + username);
153
154             Subject subject = jaasLoginCtx.getSubject();
155             Principal principal = subject.getPrincipals().iterator().next();
156             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, principal.getName());
157             request.setAttribute(LoginHandler.SUBJECT_KEY, jaasLoginCtx.getSubject());
158
159             return true;
160         } catch (LoginException e) {
161             if (log.isDebugEnabled()) {
162                 log.debug("User authentication failed", e);
163             }
164             return false;
165         }
166     }
167
168     /**
169      * A callback handler that provides static name and password data to a JAAS login process.
170      * 
171      * This handler only supports {@link NameCallback} and {@link PasswordCallback}.
172      */
173     protected class SimpleCallbackHandler implements CallbackHandler {
174
175         /** Name of the user. */
176         private String uname;
177
178         /** User's password. */
179         private String pass;
180
181         /**
182          * Constructor.
183          * 
184          * @param username The username
185          * @param password The password
186          */
187         public SimpleCallbackHandler(String username, String password) {
188             uname = username;
189             pass = password;
190         }
191
192         /**
193          * Handle a callback.
194          * 
195          * @param callbacks The list of callbacks to process.
196          * 
197          * @throws UnsupportedCallbackException If callbacks has a callback other than {@link NameCallback} or
198          *             {@link PasswordCallback}.
199          */
200         public void handle(final Callback[] callbacks) throws UnsupportedCallbackException {
201
202             if (callbacks == null || callbacks.length == 0) {
203                 return;
204             }
205
206             for (Callback cb : callbacks) {
207                 if (cb instanceof NameCallback) {
208                     NameCallback ncb = (NameCallback) cb;
209                     ncb.setName(uname);
210                 } else if (cb instanceof PasswordCallback) {
211                     PasswordCallback pcb = (PasswordCallback) cb;
212                     pcb.setPassword(pass.toCharArray());
213                 }
214             }
215         }
216     }
217 }