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
163 if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) return AuthStatus.SUCCESS;
164
165 try
166 {
167
168 if (isJSecurityCheck(uri))
169 {
170 final String username = request.getParameter(__J_USERNAME);
171 final String password = request.getParameter(__J_PASSWORD);
172
173 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
174 if (success)
175 {
176
177 String nuri=null;
178 synchronized(session)
179 {
180 nuri = (String) session.getAttribute(__J_URI);
181 }
182
183 if (nuri == null || nuri.length() == 0)
184 {
185 nuri = request.getContextPath();
186 if (nuri.length() == 0)
187 nuri = URIUtil.SLASH;
188 }
189
190 response.setContentLength(0);
191 response.sendRedirect(response.encodeRedirectURL(nuri));
192 return AuthStatus.SEND_CONTINUE;
193 }
194
195 if (LOG.isDebugEnabled()) LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
196 if (_formErrorPage == null)
197 {
198 if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
199 }
200 else
201 {
202 response.setContentLength(0);
203 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
204 }
205
206
207 return AuthStatus.SEND_FAILURE;
208 }
209
210
211
212 FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED);
213 if (form_cred != null)
214 {
215
216
217 boolean success = tryLogin(messageInfo, clientSubject, response, session, form_cred._jUserName, new Password(new String(form_cred._jPassword)));
218 if (success) { return AuthStatus.SUCCESS; }
219 }
220 else if (ssoSource != null)
221 {
222 UserInfo userInfo = ssoSource.fetch(request);
223 if (userInfo != null)
224 {
225 boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
226 if (success) { return AuthStatus.SUCCESS; }
227 }
228 }
229
230
231
232
233 if (DeferredAuthentication.isDeferred(response))
234 return AuthStatus.SUCCESS;
235
236
237
238 StringBuffer buf = request.getRequestURL();
239 if (request.getQueryString() != null)
240 buf.append("?").append(request.getQueryString());
241
242 synchronized (session)
243 {
244 session.setAttribute(__J_URI, buf.toString());
245 }
246
247 response.setContentLength(0);
248 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
249 return AuthStatus.SEND_CONTINUE;
250 }
251 catch (IOException e)
252 {
253 throw new AuthException(e.getMessage());
254 }
255 catch (UnsupportedCallbackException e)
256 {
257 throw new AuthException(e.getMessage());
258 }
259
260 }
261
262
263 public boolean isJSecurityCheck(String uri)
264 {
265 int jsc = uri.indexOf(__J_SECURITY_CHECK);
266
267 if (jsc<0)
268 return false;
269 int e=jsc+__J_SECURITY_CHECK.length();
270 if (e==uri.length())
271 return true;
272 char c = uri.charAt(e);
273 return c==';'||c=='#'||c=='/'||c=='?';
274 }
275
276 private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject,
277 HttpServletResponse response, HttpSession session,
278 String username, Password password)
279 throws AuthException, IOException, UnsupportedCallbackException
280 {
281 if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
282 {
283 char[] pwdChars = password.toString().toCharArray();
284 Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
285
286 if (!loginCallbacks.isEmpty())
287 {
288 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
289 FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal(), loginCallback.getSubject());
290 session.setAttribute(__J_AUTHENTICATED, form_cred);
291 }
292
293
294 if (ssoSource != null)
295 {
296 UserInfo userInfo = new UserInfo(username, pwdChars);
297 ssoSource.store(userInfo, response);
298 }
299 return true;
300 }
301 return false;
302 }
303
304 public boolean isLoginOrErrorPage(String pathInContext)
305 {
306 return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
307 }
308
309
310
311
312
313 private static class FormCredential implements Serializable, HttpSessionBindingListener
314 {
315 String _jUserName;
316
317 char[] _jPassword;
318
319 transient Principal _userPrincipal;
320
321 transient Subject _subject;
322
323 private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal, Subject subject)
324 {
325 this._jUserName = _jUserName;
326 this._jPassword = _jPassword;
327 this._userPrincipal = _userPrincipal;
328 this._subject = subject;
329 }
330
331 public void valueBound(HttpSessionBindingEvent event)
332 {
333 }
334
335 public void valueUnbound(HttpSessionBindingEvent event)
336 {
337 if (LOG.isDebugEnabled()) LOG.debug("Logout " + _jUserName);
338
339
340
341
342
343
344
345 }
346
347 public int hashCode()
348 {
349 return _jUserName.hashCode() + _jPassword.hashCode();
350 }
351
352 public boolean equals(Object o)
353 {
354 if (!(o instanceof FormCredential)) return false;
355 FormCredential fc = (FormCredential) o;
356 return _jUserName.equals(fc._jUserName) && Arrays.equals(_jPassword, fc._jPassword);
357 }
358
359 public String toString()
360 {
361 return "Cred[" + _jUserName + "]";
362 }
363
364 }
365
366 }