1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.security.jaspi.modules;
15
16 import java.io.IOException;
17 import java.io.Serializable;
18 import java.security.Principal;
19 import java.util.Arrays;
20 import java.util.Map;
21 import java.util.Set;
22
23 import javax.security.auth.Subject;
24 import javax.security.auth.callback.CallbackHandler;
25 import javax.security.auth.callback.UnsupportedCallbackException;
26 import javax.security.auth.message.AuthException;
27 import javax.security.auth.message.AuthStatus;
28 import javax.security.auth.message.MessageInfo;
29 import javax.security.auth.message.MessagePolicy;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32 import javax.servlet.http.HttpSession;
33 import javax.servlet.http.HttpSessionBindingEvent;
34 import javax.servlet.http.HttpSessionBindingListener;
35
36 import org.eclipse.jetty.util.security.Constraint;
37 import org.eclipse.jetty.util.security.Password;
38 import org.eclipse.jetty.security.CrossContextPsuedoSession;
39 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
40 import org.eclipse.jetty.security.authentication.LoginCallbackImpl;
41 import org.eclipse.jetty.server.Authentication;
42 import org.eclipse.jetty.util.StringUtil;
43 import org.eclipse.jetty.util.URIUtil;
44 import org.eclipse.jetty.util.log.Log;
45 import org.eclipse.jetty.util.log.Logger;
46
47
48
49
50
51 public class FormAuthModule extends BaseAuthModule
52 {
53 private static final Logger LOG = Log.getLogger(FormAuthModule.class);
54
55
56 public final static String __J_URI = "org.eclipse.jetty.util.URI";
57
58 public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth";
59
60 public final static String __J_SECURITY_CHECK = "/j_security_check";
61
62 public final static String __J_USERNAME = "j_username";
63
64 public final static String __J_PASSWORD = "j_password";
65
66
67 public static final String LOGIN_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.LoginPage";
68
69 public static final String ERROR_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.ErrorPage";
70
71 public static final String SSO_SOURCE_KEY = "org.eclipse.jetty.security.jaspi.modules.SsoSource";
72
73 private String _formErrorPage;
74
75 private String _formErrorPath;
76
77 private String _formLoginPage;
78
79 private String _formLoginPath;
80
81 private CrossContextPsuedoSession<UserInfo> ssoSource;
82
83 public FormAuthModule()
84 {
85 }
86
87 public FormAuthModule(CallbackHandler callbackHandler, String loginPage, String errorPage)
88 {
89 super(callbackHandler);
90 setLoginPage(loginPage);
91 setErrorPage(errorPage);
92 }
93
94 public FormAuthModule(CallbackHandler callbackHandler, CrossContextPsuedoSession<UserInfo> ssoSource,
95 String loginPage, String errorPage)
96 {
97 super(callbackHandler);
98 this.ssoSource = ssoSource;
99 setLoginPage(loginPage);
100 setErrorPage(errorPage);
101 }
102
103 @Override
104 public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy,
105 CallbackHandler handler, Map options)
106 throws AuthException
107 {
108 super.initialize(requestPolicy, responsePolicy, handler, options);
109 setLoginPage((String) options.get(LOGIN_PAGE_KEY));
110 setErrorPage((String) options.get(ERROR_PAGE_KEY));
111 ssoSource = (CrossContextPsuedoSession<UserInfo>) options.get(SSO_SOURCE_KEY);
112 }
113
114 private void setLoginPage(String path)
115 {
116 if (!path.startsWith("/"))
117 {
118 LOG.warn("form-login-page must start with /");
119 path = "/" + path;
120 }
121 _formLoginPage = path;
122 _formLoginPath = path;
123 if (_formLoginPath.indexOf('?') > 0) _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
124 }
125
126
127 private void setErrorPage(String path)
128 {
129 if (path == null || path.trim().length() == 0)
130 {
131 _formErrorPath = null;
132 _formErrorPage = null;
133 }
134 else
135 {
136 if (!path.startsWith("/"))
137 {
138 LOG.warn("form-error-page must start with /");
139 path = "/" + path;
140 }
141 _formErrorPage = path;
142 _formErrorPath = path;
143
144 if (_formErrorPath.indexOf('?') > 0) _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
145 }
146 }
147
148 @Override
149 public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
150 {
151
152 HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
153 HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
154 String uri = request.getRequestURI();
155 if (uri==null)
156 uri=URIUtil.SLASH;
157
158 boolean mandatory = isMandatory(messageInfo);
159 mandatory |= isJSecurityCheck(uri);
160 HttpSession session = request.getSession(mandatory);
161
162 System.err.println("FormAuthModule.validateRequest(info,subject,serviceSubject) for uri="+uri+" mandatory="+mandatory+" isLoginOrError="+isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())));
163
164
165 if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) return AuthStatus.SUCCESS;
166
167 try
168 {
169
170 if (isJSecurityCheck(uri))
171 {
172 final String username = request.getParameter(__J_USERNAME);
173 final String password = request.getParameter(__J_PASSWORD);
174 System.err.println("Try login username="+username+" password="+password);
175 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
176 if (success)
177 {
178
179 String nuri=null;
180 synchronized(session)
181 {
182 nuri = (String) session.getAttribute(__J_URI);
183 }
184
185 if (nuri == null || nuri.length() == 0)
186 {
187 nuri = request.getContextPath();
188 if (nuri.length() == 0)
189 nuri = URIUtil.SLASH;
190 }
191
192 System.err.println("FormAuthModule succesful login, sending redirect to "+nuri);
193 response.setContentLength(0);
194 response.sendRedirect(response.encodeRedirectURL(nuri));
195 return AuthStatus.SEND_CONTINUE;
196 }
197
198 if (LOG.isDebugEnabled()) LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
199 if (_formErrorPage == null)
200 {
201 if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
202 }
203 else
204 {
205 response.setContentLength(0);
206 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
207 }
208
209
210 return AuthStatus.SEND_FAILURE;
211 }
212
213
214
215 FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED);
216 if (form_cred != null)
217 {
218 System.err.println("Form cred: form.username="+form_cred._jUserName+" form.pwd="+new String(form_cred._jPassword));
219
220
221
222 boolean success = tryLogin(messageInfo, clientSubject, response, session, form_cred._jUserName, new Password(new String(form_cred._jPassword)));
223 if (success) { return AuthStatus.SUCCESS; }
224 }
225 else if (ssoSource != null)
226 {
227 UserInfo userInfo = ssoSource.fetch(request);
228 if (userInfo != null)
229 {
230 boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
231 if (success) { return AuthStatus.SUCCESS; }
232 }
233 }
234
235
236
237
238 if (DeferredAuthentication.isDeferred(response))
239 return AuthStatus.SUCCESS;
240
241
242
243 StringBuffer buf = request.getRequestURL();
244 if (request.getQueryString() != null)
245 buf.append("?").append(request.getQueryString());
246
247 synchronized (session)
248 {
249 session.setAttribute(__J_URI, buf.toString());
250 }
251
252 System.err.println("Redirecting to login page "+_formLoginPage+" and remembering juri="+buf.toString());
253 response.setContentLength(0);
254 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
255 return AuthStatus.SEND_CONTINUE;
256 }
257 catch (IOException e)
258 {
259 throw new AuthException(e.getMessage());
260 }
261 catch (UnsupportedCallbackException e)
262 {
263 throw new AuthException(e.getMessage());
264 }
265
266 }
267
268
269 public boolean isJSecurityCheck(String uri)
270 {
271 int jsc = uri.indexOf(__J_SECURITY_CHECK);
272
273 if (jsc<0)
274 return false;
275 int e=jsc+__J_SECURITY_CHECK.length();
276 if (e==uri.length())
277 return true;
278 char c = uri.charAt(e);
279 return c==';'||c=='#'||c=='/'||c=='?';
280 }
281
282 private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject,
283 HttpServletResponse response, HttpSession session,
284 String username, Password password)
285 throws AuthException, IOException, UnsupportedCallbackException
286 {
287 if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
288 {
289 char[] pwdChars = password.toString().toCharArray();
290 Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
291 System.err.println("FormAuthModule, LoginCallbackImpl.isEmpty="+loginCallbacks.isEmpty());
292 if (!loginCallbacks.isEmpty())
293 {
294 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
295 FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal(), loginCallback.getSubject());
296
297 session.setAttribute(__J_AUTHENTICATED, form_cred);
298 }
299
300
301 if (ssoSource != null)
302 {
303 UserInfo userInfo = new UserInfo(username, pwdChars);
304 ssoSource.store(userInfo, response);
305 }
306 return true;
307 }
308 return false;
309 }
310
311 public boolean isLoginOrErrorPage(String pathInContext)
312 {
313 System.err.println("ISLOGINORERRORPAGE? "+pathInContext+" error: "+_formErrorPath+" login:"+_formLoginPath);
314 return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
315 }
316
317
318
319
320
321 private static class FormCredential implements Serializable, HttpSessionBindingListener
322 {
323 String _jUserName;
324
325 char[] _jPassword;
326
327 transient Principal _userPrincipal;
328
329 transient Subject _subject;
330
331 private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal, Subject subject)
332 {
333 this._jUserName = _jUserName;
334 this._jPassword = _jPassword;
335 this._userPrincipal = _userPrincipal;
336 this._subject = subject;
337 }
338
339 public void valueBound(HttpSessionBindingEvent event)
340 {
341 }
342
343 public void valueUnbound(HttpSessionBindingEvent event)
344 {
345 if (LOG.isDebugEnabled()) LOG.debug("Logout " + _jUserName);
346
347
348
349
350
351
352
353 }
354
355 public int hashCode()
356 {
357 return _jUserName.hashCode() + _jPassword.hashCode();
358 }
359
360 public boolean equals(Object o)
361 {
362 if (!(o instanceof FormCredential)) return false;
363 FormCredential fc = (FormCredential) o;
364 return _jUserName.equals(fc._jUserName) && Arrays.equals(_jPassword, fc._jPassword);
365 }
366
367 public String toString()
368 {
369 return "Cred[" + _jUserName + "]";
370 }
371
372 }
373
374 }