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 }