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