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
218 if (form_cred._subject == null)
219 return AuthStatus.SEND_FAILURE;
220 Set<Object> credentials = form_cred._subject.getPrivateCredentials();
221 if (credentials == null || credentials.isEmpty())
222 return AuthStatus.SEND_FAILURE;
223
224 clientSubject.getPrivateCredentials().addAll(credentials);
225
226
227 return AuthStatus.SUCCESS;
228 }
229 else if (ssoSource != null)
230 {
231 UserInfo userInfo = ssoSource.fetch(request);
232 if (userInfo != null)
233 {
234 boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
235 if (success) { return AuthStatus.SUCCESS; }
236 }
237 }
238
239
240
241
242 if (DeferredAuthentication.isDeferred(response))
243 return AuthStatus.SUCCESS;
244
245
246
247 StringBuffer buf = request.getRequestURL();
248 if (request.getQueryString() != null)
249 buf.append("?").append(request.getQueryString());
250
251 synchronized (session)
252 {
253 session.setAttribute(__J_URI, buf.toString());
254 }
255
256 response.setContentLength(0);
257 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
258 return AuthStatus.SEND_CONTINUE;
259 }
260 catch (IOException e)
261 {
262 throw new AuthException(e.getMessage());
263 }
264 catch (UnsupportedCallbackException e)
265 {
266 throw new AuthException(e.getMessage());
267 }
268
269 }
270
271
272 public boolean isJSecurityCheck(String uri)
273 {
274 int jsc = uri.indexOf(__J_SECURITY_CHECK);
275
276 if (jsc<0)
277 return false;
278 int e=jsc+__J_SECURITY_CHECK.length();
279 if (e==uri.length())
280 return true;
281 char c = uri.charAt(e);
282 return c==';'||c=='#'||c=='/'||c=='?';
283 }
284
285 private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject,
286 HttpServletResponse response, HttpSession session,
287 String username, Password password)
288 throws AuthException, IOException, UnsupportedCallbackException
289 {
290 if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
291 {
292 char[] pwdChars = password.toString().toCharArray();
293 Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
294
295 if (!loginCallbacks.isEmpty())
296 {
297 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
298 FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal(), loginCallback.getSubject());
299 session.setAttribute(__J_AUTHENTICATED, form_cred);
300 }
301
302
303 if (ssoSource != null)
304 {
305 UserInfo userInfo = new UserInfo(username, pwdChars);
306 ssoSource.store(userInfo, response);
307 }
308 return true;
309 }
310 return false;
311 }
312
313 public boolean isLoginOrErrorPage(String pathInContext)
314 {
315 return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
316 }
317
318
319
320
321
322 private static class FormCredential implements Serializable, HttpSessionBindingListener
323 {
324 String _jUserName;
325
326 char[] _jPassword;
327
328 transient Principal _userPrincipal;
329
330 transient Subject _subject;
331
332 private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal, Subject subject)
333 {
334 this._jUserName = _jUserName;
335 this._jPassword = _jPassword;
336 this._userPrincipal = _userPrincipal;
337 this._subject = subject;
338 }
339
340 public void valueBound(HttpSessionBindingEvent event)
341 {
342 }
343
344 public void valueUnbound(HttpSessionBindingEvent event)
345 {
346 if (LOG.isDebugEnabled()) LOG.debug("Logout " + _jUserName);
347
348
349
350
351
352
353
354 }
355
356 public int hashCode()
357 {
358 return _jUserName.hashCode() + _jPassword.hashCode();
359 }
360
361 public boolean equals(Object o)
362 {
363 if (!(o instanceof FormCredential)) return false;
364 FormCredential fc = (FormCredential) o;
365 return _jUserName.equals(fc._jUserName) && Arrays.equals(_jPassword, fc._jPassword);
366 }
367
368 public String toString()
369 {
370 return "Cred[" + _jUserName + "]";
371 }
372
373 }
374
375 }