1
2
3
4
5
6
7
8
9
10
11
12
13 package org.abstracthorizon.danube.http.auth;
14
15 import org.abstracthorizon.danube.connection.Connection;
16 import org.abstracthorizon.danube.connection.ConnectionException;
17 import org.abstracthorizon.danube.http.HTTPConnection;
18 import org.abstracthorizon.danube.http.HTTPContext;
19 import org.abstracthorizon.danube.http.Status;
20 import org.abstracthorizon.danube.http.session.HTTPSessionManager;
21 import org.abstracthorizon.danube.http.session.Session;
22 import org.abstracthorizon.danube.http.session.SimpleSessionManager;
23 import org.abstracthorizon.danube.http.util.Base64;
24 import org.abstracthorizon.danube.support.RuntimeIOException;
25
26 import java.io.IOException;
27 import java.security.PrivilegedActionException;
28 import java.security.PrivilegedExceptionAction;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Map;
32
33 import javax.security.auth.Subject;
34 import javax.security.auth.callback.Callback;
35 import javax.security.auth.callback.CallbackHandler;
36 import javax.security.auth.callback.ConfirmationCallback;
37 import javax.security.auth.callback.NameCallback;
38 import javax.security.auth.callback.PasswordCallback;
39 import javax.security.auth.callback.TextOutputCallback;
40 import javax.security.auth.callback.UnsupportedCallbackException;
41 import javax.security.auth.login.LoginContext;
42 import javax.security.auth.login.LoginException;
43
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47
48
49
50
51
52
53
54 public class JAASAuthenticatedHTTPContext extends HTTPContext {
55
56
57
58 protected final Logger logger = LoggerFactory.getLogger(JAASAuthenticatedHTTPContext.class);
59
60
61
62 public static final String AUTHORIZATION_DATA_ATTRIBUTE = "org.abstracthorizon.danube.http.auth.Subject";
63
64
65 public static final String AUTHORIZATION_REQUEST_HEADER = "Authorization";
66
67
68 public static final String AUTHORIZATION_RESPONSE_HEADER = "WWW-Authenticate";
69
70
71 public static final int DEFAULT_CACHE_TIMEOUT = 10 * 60 * 1000;
72
73
74 public static final int DEFAULT_MINIMUM_SCAN_PERIOD = 10 * 1000;
75
76
77 protected HTTPSessionManager sessionManager;
78
79
80 protected String realm;
81
82
83 protected String loginContextName;
84
85
86 protected LoginContext loginContext;
87
88
89 protected Map<String, AuthData> cachedAuth = new HashMap<String, AuthData>();
90
91
92 protected int cacheTimeout = DEFAULT_CACHE_TIMEOUT;
93
94
95 protected int minScanPeriod = DEFAULT_MINIMUM_SCAN_PERIOD;
96
97
98 protected long lastScan;
99
100
101 protected boolean forceAuthorisation = true;
102
103
104
105
106 public JAASAuthenticatedHTTPContext() {
107 }
108
109
110
111
112
113
114
115
116
117 public void handleConnection(final Connection connection) throws ConnectionException {
118 Subject subject = null;
119 Session session = null;
120 boolean fromSession = false;
121 HTTPSessionManager sessionManager = getSessionManager();
122 if (sessionManager != null) {
123 session = (Session)sessionManager.findSession(connection, false);
124 if (session != null) {
125 subject = (Subject)session.getAttributes().get(AUTHORIZATION_DATA_ATTRIBUTE);
126 if (subject != null) {
127 fromSession = true;
128 }
129 }
130 }
131
132 HTTPConnection httpConnection = (HTTPConnection)connection.adapt(HTTPConnection.class);
133 if (subject == null) {
134
135 String authHeader = httpConnection.getRequestHeaders().getOnly(AUTHORIZATION_REQUEST_HEADER);
136 if (authHeader != null) {
137 if (authHeader.startsWith("Basic ")) {
138 String base64 = authHeader.substring(6);
139 subject = authorise(base64);
140 }
141 }
142 }
143
144 if (subject != null) {
145 if (!fromSession && (session != null)) {
146 session.getAttributes().put(AUTHORIZATION_DATA_ATTRIBUTE, subject);
147 }
148 try {
149 Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
150 public Object run() throws Exception {
151 superHandleConnection(connection);
152 return null;
153 }
154 });
155 } catch (PrivilegedActionException e) {
156 if (e.getException() instanceof ConnectionException) {
157 throw (ConnectionException)e.getException();
158 } else if (e.getException() instanceof IOException) {
159 throw new RuntimeIOException((IOException)e.getException());
160 } else {
161 throw new ConnectionException(e);
162 }
163 }
164 } else {
165 if (forceAuthorisation) {
166 String oldComponentPath = httpConnection.getComponentPath();
167 httpConnection.addComponentPathToContextPath();
168
169
170
171 String realm = getRealm();
172 if (realm == null) {
173 realm = oldComponentPath;
174 }
175 httpConnection.getResponseHeaders().putOnly(AUTHORIZATION_RESPONSE_HEADER, "Basic realm=\"" + realm + "\"");
176 httpConnection.setResponseStatus(Status.UNAUTHORIZED);
177 } else {
178 try {
179 superHandleConnection(connection);
180 } catch (IOException e) {
181 throw new RuntimeIOException(e);
182 }
183 }
184 }
185 }
186
187
188
189
190
191
192 protected void superHandleConnection(Connection connection) throws IOException {
193 super.handleConnection(connection);
194 }
195
196
197
198
199
200
201 protected Subject authorise(String base64) {
202 AuthData authData = null;
203
204 synchronized (cachedAuth) {
205 long now = System.currentTimeMillis() - minScanPeriod;
206 if (lastScan + minScanPeriod < now) {
207 Iterator<AuthData> it = cachedAuth.values().iterator();
208 while (it.hasNext()) {
209 authData = it.next();
210 if (authData.lastAccessed + cacheTimeout < now) {
211 it.remove();
212 }
213 }
214 lastScan = System.currentTimeMillis();
215 }
216 authData = cachedAuth.get(base64);
217 }
218
219 if (authData != null) {
220 authData.lastAccessed = System.currentTimeMillis();
221 return authData.subject;
222 }
223
224 String userPass = Base64.decode(base64);
225 int i = userPass.indexOf(':');
226 if (i < 0) {
227 return null;
228 }
229
230 final String user = userPass.substring(0, i);
231 final char[] pass = userPass.substring(i+1).toCharArray();
232
233 LoginContext loginContext;
234 try {
235
236 loginContext = new LoginContext(getLoginContextName(), new CallbackHandler() {
237
238 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
239 for (int i = 0; i < callbacks.length; i++) {
240 if (callbacks[i] instanceof TextOutputCallback) {
241 } else if (callbacks[i] instanceof ConfirmationCallback) {
242 } else if (callbacks[i] instanceof NameCallback) {
243 NameCallback nameCallback = (NameCallback)callbacks[i];
244 nameCallback.setName(user);
245 } else if (callbacks[i] instanceof PasswordCallback) {
246 PasswordCallback passwordCallback = (PasswordCallback)callbacks[i];
247 passwordCallback.setPassword(pass);
248 } else {
249 throw new UnsupportedCallbackException
250 (callbacks[i], "Unrecognized Callback");
251 }
252 }
253 }
254 });
255 if (loginContext == null) {
256 return null;
257 } else {
258 setLoginContext(loginContext);
259 }
260
261 logger.debug("Trying to authenticate user " + user);
262 loginContext.login();
263 logger.debug("Successfully authenticated user " + user);
264 } catch (LoginException e) {
265 logger.debug("Exception trying to get LoginContext " + getLoginContextName(), e);
266 return null;
267 }
268 Subject subject = loginContext.getSubject();
269 synchronized (cachedAuth) {
270 authData = new AuthData();
271 authData.lastAccessed = System.currentTimeMillis();
272 authData.subject = subject;
273 cachedAuth.put(base64, authData);
274 }
275 return subject;
276 }
277
278
279
280
281
282 public HTTPSessionManager getSessionManager() {
283 if (sessionManager == null) {
284 sessionManager = new SimpleSessionManager();
285 }
286 return sessionManager;
287 }
288
289
290
291
292
293 public void setSessionManager(HTTPSessionManager sessionManager) {
294 this.sessionManager = sessionManager;
295 }
296
297
298
299
300
301 public String getRealm() {
302 return realm;
303 }
304
305
306
307
308
309
310 public void setRealm(String realm) {
311 this.realm = realm;
312 }
313
314
315
316
317
318 public String getLoginContextName() {
319 return loginContextName;
320 }
321
322
323
324
325
326 public void setLoginContextName(String loginContextName) {
327 this.loginContextName = loginContextName;
328 }
329
330
331
332
333
334 public LoginContext getLoginContext() {
335 return loginContext;
336 }
337
338
339
340
341
342 public void setLoginContext(LoginContext loginContext) {
343 this.loginContext = loginContext;
344 }
345
346
347
348
349
350 public int getCacheTimeout() {
351 return cacheTimeout;
352 }
353
354
355
356
357
358 public void setCacheTimeout(int cacheTimeout) {
359 this.cacheTimeout = cacheTimeout;
360 }
361
362
363
364
365
366 public int getMinimumScanPeriod() {
367 return minScanPeriod;
368 }
369
370
371
372
373
374 public void setMinimumScanPeriod(int minScanPeriod) {
375 this.minScanPeriod = minScanPeriod;
376 }
377
378 public boolean getForceAuthorisation() {
379 return forceAuthorisation;
380 }
381
382 public void setForceAuthorisation(boolean forceAuthorisation) {
383 this.forceAuthorisation = forceAuthorisation;
384 }
385
386
387
388
389
390 protected class AuthData {
391
392 public long lastAccessed;
393 public Subject subject;
394
395 }
396 }