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