KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > jguard > jee > authentication > callbacks > HttpCallbackHandler


1 /*
2 jGuard is a security framework based on top of jaas (java authentication and authorization security).
3 it is written for web applications, to resolve simply, access control problems.
4 version $Name: $
5 http://sourceforge.net/projects/jguard/
6
7 Copyright (C) 2004 Charles GAY
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23
24 jGuard project home page:
25 http://sourceforge.net/projects/jguard/
26
27 */

28 package net.sf.jguard.jee.authentication.callbacks;
29
30 import java.io.IOException JavaDoc;
31 import java.io.UnsupportedEncodingException JavaDoc;
32 import java.security.cert.X509Certificate JavaDoc;
33 import java.util.Arrays JavaDoc;
34 import java.util.Iterator JavaDoc;
35 import java.util.List JavaDoc;
36
37 import javax.security.auth.Subject JavaDoc;
38 import javax.security.auth.callback.Callback JavaDoc;
39 import javax.security.auth.callback.CallbackHandler JavaDoc;
40 import javax.security.auth.callback.NameCallback JavaDoc;
41 import javax.security.auth.callback.PasswordCallback JavaDoc;
42 import javax.security.auth.callback.UnsupportedCallbackException JavaDoc;
43 import javax.servlet.FilterChain JavaDoc;
44 import javax.servlet.ServletException JavaDoc;
45 import javax.servlet.ServletRequest JavaDoc;
46 import javax.servlet.ServletResponse JavaDoc;
47 import javax.servlet.http.HttpServletRequest JavaDoc;
48 import javax.servlet.http.HttpServletResponse JavaDoc;
49 import javax.servlet.http.HttpSession JavaDoc;
50
51 import net.sf.jguard.ext.SecurityConstants;
52 import net.sf.jguard.ext.authentication.callbacks.CertificatesCallback;
53 import net.sf.jguard.ext.authentication.callbacks.JCaptchaCallback;
54 import net.sf.jguard.ext.authentication.certificates.CertificateConverter;
55 import net.sf.jguard.jee.authentication.http.HttpAuthenticationUtils;
56 import net.sf.jguard.jee.authentication.http.HttpConstants;
57
58 import org.apache.commons.logging.Log;
59 import org.apache.commons.logging.LogFactory;
60 import org.bouncycastle.util.encoders.Base64;
61
62 import com.octo.captcha.service.CaptchaService;
63
64 /**
65  * handle grabbing credentials from an HTTP request.
66  * @author <a HREF="mailto:diabolo512@users.sourceforge.net ">Charles Gay</a>
67  */

68 public class HttpCallbackHandler implements CallbackHandler JavaDoc{
69
70     private static final String JavaDoc JAVAX_SERVLET_REQUEST_X509CERTIFICATE = "javax.servlet.request.X509Certificate";
71     private static final String JavaDoc DIGEST_REALM = "Digest realm=\"";
72     public static final String JavaDoc AUTHORIZATION = "Authorization";
73     private static final String JavaDoc BASIC_REALM = "Basic realm=\"";
74     private static final String JavaDoc NO_CACHE_AUTHORIZATION = "no-cache=\"Authorization\"";
75     private static final String JavaDoc CACHE_CONTROL = "Cache-Control";
76     private static final String JavaDoc WWW_AUTHENTICATE = "WWW-Authenticate";
77     private static final String JavaDoc BASIC = "Basic ";
78     private static final String JavaDoc ISO_8859_1 = "ISO-8859-1";
79     /** Logger for this class */
80     private static final Log logger = LogFactory.getLog(HttpCallbackHandler.class);
81     private HttpServletRequest JavaDoc httpRequest;
82     private HttpServletResponse JavaDoc httpResponse;
83     private String JavaDoc authScheme=HttpConstants.FORM_AUTH;
84     private static String JavaDoc loginField="login";
85     private static String JavaDoc passwordField="password";
86     private boolean afterRegistration;
87
88
89     /**
90      * constructor required by javadoc of the CallbackHandler interface.
91      */

92     public HttpCallbackHandler(){
93
94     }
95
96     /**
97      * constructor.
98      * @param request
99      * @param response
100      * @param authScheme
101      */

102     public HttpCallbackHandler(HttpServletRequest JavaDoc request,HttpServletResponse JavaDoc response,String JavaDoc authScheme){
103         this.httpRequest = request;
104         this.httpResponse = response;
105         this.authScheme = authScheme;
106
107
108     }
109
110
111     /**
112      * extract from the HttpServletRequest client's credentials.
113      * if those are not recognised, we put the challenge in the
114      * HttpServletResponse.
115      */

116     public void handle(Callback JavaDoc[] callbacks) throws IOException JavaDoc, UnsupportedCallbackException JavaDoc {
117             boolean httpRelatedAuthScheme = false;
118             logger.debug("authSchemeItem="+authScheme);
119             String JavaDoc[] schemes = authScheme.split(",");
120             List JavaDoc authSchemes = Arrays.asList(schemes);
121             Iterator JavaDoc itAutSchemes = authSchemes.iterator();
122             while(itAutSchemes.hasNext()){
123                 String JavaDoc scheme = (String JavaDoc)itAutSchemes.next();
124                 //FORM, BASIC, and DIGEST are mutual exclusive
125
if(!httpRelatedAuthScheme && HttpConstants.FORM_AUTH.equalsIgnoreCase(scheme)){
126                     grabFormCredentials(this.httpRequest,callbacks);
127                     httpRelatedAuthScheme = true;
128                 }else if(!httpRelatedAuthScheme && HttpConstants.BASIC_AUTH.equalsIgnoreCase(scheme)){
129                     grabBasicCredentials(this.httpRequest,callbacks);
130                     httpRelatedAuthScheme = true;
131                 }else if(!httpRelatedAuthScheme && HttpConstants.DIGEST_AUTH.equalsIgnoreCase(scheme)){
132                     grabDigestCredentials(this.httpRequest,callbacks);
133                     httpRelatedAuthScheme = true;
134                 }
135                 //CLIENT_CERT can be used with another authentication mechanism
136
//defined above
137
if(HttpConstants.CLIENT_CERT_AUTH.equalsIgnoreCase(scheme)){
138                     boolean certificatesFound = grabClientCertCredentials(this.httpRequest,callbacks);
139                     if(!certificatesFound){
140                         logger.info(" X509 certificates are not found ");
141                     }
142                 }
143             }
144
145     }
146
147     public HttpServletRequest JavaDoc getHttpRequest() {
148         return httpRequest;
149     }
150
151
152     public void setHttpRequest(HttpServletRequest JavaDoc httpRequest) {
153         this.httpRequest = httpRequest;
154     }
155
156     public HttpServletResponse JavaDoc getHttpResponse() {
157         return httpResponse;
158     }
159
160     public void setHttpResponse(HttpServletResponse JavaDoc httpResponse) {
161         this.httpResponse = httpResponse;
162     }
163
164     /**
165      * construct a header value to simulate a Basic authentication with the provided credentials.
166      * @param login
167      * @param password
168      * @param encoding
169      * @return header
170      */

171     public static String JavaDoc buildBasicAuthHeader(String JavaDoc login,String JavaDoc password,String JavaDoc encoding){
172         if(encoding==null){
173             encoding=HttpCallbackHandler.ISO_8859_1;
174         }
175         StringBuffer JavaDoc decodedString = new StringBuffer JavaDoc();
176         decodedString.append(login);
177         decodedString.append(" : ");
178         decodedString.append(password);
179         String JavaDoc encodedString;
180         try {
181             encodedString = new String JavaDoc(Base64.encode(decodedString.toString().getBytes(encoding)));
182         } catch (UnsupportedEncodingException JavaDoc e) {
183             encodedString = new String JavaDoc(Base64.encode(decodedString.toString().getBytes()));
184         }
185         StringBuffer JavaDoc header = new StringBuffer JavaDoc();
186         header.append(HttpCallbackHandler.BASIC);
187         header.append(encodedString);
188         header.append("==");
189         return header.toString();
190     }
191
192     /**
193      * send to the client the BASIC challenge into the response, according to the RFC 2617.
194      * @param response reponse send to the Client
195      * @param realmName realm owned by the server => specify what kind of credential the user should provide
196      */

197     public static void buildBasicChallenge(HttpServletResponse JavaDoc response,String JavaDoc realmName){
198         StringBuffer JavaDoc responseValue= new StringBuffer JavaDoc();
199         responseValue.append(HttpCallbackHandler.BASIC_REALM);
200         responseValue.append(realmName);
201         responseValue.append("\"");
202         response.setHeader(HttpCallbackHandler.WWW_AUTHENTICATE,responseValue.toString());
203         response.setHeader(HttpCallbackHandler.CACHE_CONTROL, HttpCallbackHandler.NO_CACHE_AUTHORIZATION);
204         response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
205     }
206     
207     
208     /**
209      * parse into the HttpServletRequest the user and password field,
210      * and authenticate the user with these credentials using the <b>NON SECURE</> BASIC method.
211      * @param request request send by the client.
212      * @param callbacks
213      * @return authentication's result. <i>true</i> for authentication success, <i>false</i> otherwise.
214      */

215     private boolean grabBasicCredentials(HttpServletRequest JavaDoc request,Callback JavaDoc[] callbacks){
216         boolean result = false;
217         String JavaDoc login="";
218         String JavaDoc password="";
219         //user and password are encoded in Base64
220
String JavaDoc encodedLoginAndPwd = request.getHeader(HttpCallbackHandler.AUTHORIZATION);
221
222         if(encodedLoginAndPwd==null ||encodedLoginAndPwd.equals("")){
223             login =SecurityConstants.GUEST;
224             password =SecurityConstants.GUEST;
225
226         }else{
227             encodedLoginAndPwd = encodedLoginAndPwd.substring(6).trim();
228             String JavaDoc decodedLoginAndPassword = null;
229
230                 String JavaDoc encoding = request.getCharacterEncoding();
231                 if(encoding==null){
232                     encoding=HttpCallbackHandler.ISO_8859_1;
233                 }
234                 logger.debug(encoding);
235
236                 try {
237                     decodedLoginAndPassword = new String JavaDoc(Base64.decode(encodedLoginAndPwd.getBytes()),encoding);
238                 } catch (UnsupportedEncodingException JavaDoc e) {
239                     e.printStackTrace();
240                     logger.error(" encoding "+encoding+" is not supported by the platform ");
241                 }
242
243             String JavaDoc[] parts = decodedLoginAndPassword.split(":");
244             if(parts.length == 2 ){
245                 login = parts[0].trim();
246                 password = parts[1].trim();
247
248                 result = true;
249             }
250             if((login=="" && password=="")||(parts.length==0)){
251                 login =SecurityConstants.GUEST;
252                 password =SecurityConstants.GUEST;
253             }
254
255         }
256
257         fillBasicCredentials(callbacks,login,password);
258         return result;
259     }
260
261      /**
262      * grab user credentials from request in the 'form' authentication metod.
263      * @param request request send by the client
264      * @param callbacks
265      * @return authentication result : <b>true</b> when authentication succeed,<b>false</b> when authentication fails.
266      */

267     private boolean grabFormCredentials(HttpServletRequest JavaDoc request,Callback JavaDoc[] callbacks){
268         boolean result = false;
269         HttpSession JavaDoc session = request.getSession();
270
271         for(int i=0;i<callbacks.length;i++){
272             if(callbacks[i] instanceof NameCallback JavaDoc){
273                 NameCallback JavaDoc nc = (NameCallback JavaDoc)callbacks[i];
274                 String JavaDoc login =httpRequest.getParameter(loginField);
275                 nc.setName(login);
276             }else if(callbacks[i] instanceof PasswordCallback JavaDoc){
277                 PasswordCallback JavaDoc pc = (PasswordCallback JavaDoc)callbacks[i];
278                 String JavaDoc strPwd = httpRequest.getParameter(passwordField);
279                  if(strPwd!= null &&strPwd!=""){
280                   pc.setPassword(strPwd.toCharArray());
281                  }else{
282                   pc.setPassword(null);
283                  }
284             }else if(callbacks[i] instanceof JCaptchaCallback){
285                 JCaptchaCallback pc = (JCaptchaCallback)callbacks[i];
286                 pc.setCaptchaAnswer(httpRequest.getParameter(SecurityConstants.CAPTCHA_ANSWER));
287                 pc.setCaptchaService((CaptchaService)session.getServletContext().getAttribute(SecurityConstants.CAPTCHA_SERVICE));
288                 Subject JavaDoc subject = ((HttpAuthenticationUtils)session.getAttribute(HttpConstants.AUTHN_UTILS)).getSubject();
289                 if(subject==null ||this.isAfterRegistration()){
290                     pc.setSkipJCaptchaChallenge(true);
291                 }
292
293                 pc.setSessionID(session.getId());
294             }
295         }
296         result = true;
297
298
299         return result;
300     }
301
302      /**
303      * grab user credentials from request in the 'digest' authentication metod.
304      * @param request request send by the client
305      * @param callbacks
306      * @return authentication result : <b>true</b> when authentication succeed,<b>false</b> when authentication fails.
307      */

308     private boolean grabDigestCredentials(HttpServletRequest JavaDoc request,Callback JavaDoc[] callbacks){
309         boolean result = false;
310         String JavaDoc login = "";
311         String JavaDoc password = "";
312         //all users must be authenticated
313
//unless when the user send a wrong login or/and password
314
//=> he is redirected to the logonPage
315
if(login==null || password == null ){
316            login =SecurityConstants.GUEST;
317            password =SecurityConstants.GUEST;
318         }else{
319             //TODO implements digest authentication
320
result = true;
321         }
322         return result;
323     }
324
325     /**
326      * grab user credentials from request in the 'clientCert' authentication metod.
327      * @param request
328      * @param callbacks
329      * @return <code>true</code> if successfull, <code>false</code> otherwise
330      */

331     private boolean grabClientCertCredentials(HttpServletRequest JavaDoc request,Callback JavaDoc[] callbacks) {
332         if(!request.isSecure()){
333             logger.warn(" certificate-based authentication MUST be do in secure mode ");
334             logger.warn(" but connection is do with the non secured protocol "+request.getScheme());
335             return false;
336         }
337
338         X509Certificate JavaDoc[] certificates = null;
339         javax.security.cert.X509Certificate[] oldCerts = null;
340         Object JavaDoc[] objects = (Object JavaDoc[]) request.getAttribute(HttpCallbackHandler.JAVAX_SERVLET_REQUEST_X509CERTIFICATE);
341         
342         if(objects == null || objects.length==0){
343             return false;
344         }
345         
346         if(objects instanceof X509Certificate JavaDoc[]) {
347             certificates= (X509Certificate JavaDoc[]) objects;
348         //convert old X509 certificates into new X509 certificates
349
}else if(objects instanceof javax.security.cert.X509Certificate[]) {
350             oldCerts = (javax.security.cert.X509Certificate[])objects;
351             List JavaDoc newCerts = null;
352             for(int i =0;i<oldCerts.length;i++){
353              newCerts = Arrays.asList(certificates);
354              newCerts.add(CertificateConverter.convertOldToNew(oldCerts[i]));
355             }
356             certificates = (X509Certificate JavaDoc[]) newCerts.toArray();
357         }else{
358             logger.warn(" X509certificates are needed but not provided by the client ");
359             return false;
360         }
361         fillCertCredentials(callbacks,certificates);
362
363         return true;
364     }
365
366     private void fillBasicCredentials(Callback JavaDoc[] callbacks, String JavaDoc login, String JavaDoc password) {
367         for(int i=0;i<callbacks.length;i++){
368             if(callbacks[i] instanceof NameCallback JavaDoc){
369                 NameCallback JavaDoc nc = (NameCallback JavaDoc)callbacks[i];
370                 nc.setName(login);
371
372             }else if(callbacks[i] instanceof PasswordCallback JavaDoc){
373                 PasswordCallback JavaDoc pc = (PasswordCallback JavaDoc)callbacks[i];
374                 pc.setPassword(password.toCharArray());
375             }else if (callbacks[i] instanceof JCaptchaCallback){
376                 JCaptchaCallback jc = (JCaptchaCallback)callbacks[i];
377                 //we skip JCaptcha because we cannot provide
378
//CAPTCHA challenge through BASIC authentication
379
jc.setSkipJCaptchaChallenge(true);
380             }
381         }
382     }
383
384
385     private void fillCertCredentials(Callback JavaDoc[] callbacks,X509Certificate JavaDoc[] certificates) {
386         for(int i=0;i<callbacks.length;i++){
387             if(callbacks[i] instanceof CertificatesCallback){
388                 CertificatesCallback cc = (CertificatesCallback)callbacks[i];
389                 cc.setCertificates(certificates);
390                 break;
391             }
392         }
393     }
394     
395     public static void buildFormChallenge(FilterChain JavaDoc chain,ServletRequest JavaDoc req,ServletResponse JavaDoc res) throws IOException JavaDoc, ServletException JavaDoc{
396         chain.doFilter(req,res);
397     }
398
399     /**
400      * send to the client the DIGEST challenge into the response, according to the RFC 2617.
401      * @param response reponse send to the Client
402      * @param token realm owned by the server => specify what kind of credential the user should provide
403      */

404     public static void buildDigestChallenge(HttpServletRequest JavaDoc request,HttpServletResponse JavaDoc response, String JavaDoc realm) {
405             //TODO buildDigestChallenge method is not complete
406
StringBuffer JavaDoc responseValue= new StringBuffer JavaDoc();
407             //what about domain which defines the protection space?
408

409             //realm
410
responseValue.append(HttpCallbackHandler.DIGEST_REALM);
411             responseValue.append(realm);
412             responseValue.append("\"");
413             responseValue.append(",");
414             //quality of protection qop
415
responseValue.append("qop=\"");
416             responseValue.append(getQop());
417             responseValue.append("\"");
418             responseValue.append(",");
419             
420             responseValue.append("nonce=\"");
421             responseValue.append(getNonce(request));
422             responseValue.append("\"");
423             responseValue.append(",");
424             //opaque
425
responseValue.append("opaque=");
426             responseValue.append("\"");
427             responseValue.append(getOpaque());
428             responseValue.append("\"");
429             //algorithm
430
responseValue.append("algorithm=");
431             responseValue.append("\"");
432             responseValue.append(getAlgorithm());
433             responseValue.append("\"");
434             //stale
435
responseValue.append("stale=");
436             responseValue.append("\"");
437             responseValue.append(getStale());
438             responseValue.append("\"");
439             response.setHeader(HttpCallbackHandler.WWW_AUTHENTICATE,responseValue.toString());
440             response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
441     }
442     
443     /**
444      * A flag, indicating that the previous request from the client was
445      rejected because the nonce value was stale. If stale is TRUE
446      (case-insensitive), the client may wish to simply retry the request
447      with a new encrypted response, without reprompting the user for a
448      new username and password. The server should only set stale to TRUE
449      if it receives a request for which the nonce is invalid but with a
450      valid digest for that nonce (indicating that the client knows the
451      correct username/password). If stale is FALSE, or anything other
452      than TRUE, or the stale directive is not present, the username
453      and/or password are invalid, and new values must be obtained
454      * @return
455      */

456     private static String JavaDoc getStale() {
457         return "false";
458     }
459
460     /**
461      * This directive is optional, but is made so only for backward
462          compatibility with RFC 2069 [6]; it SHOULD be used by all
463          implementations compliant with this version of the Digest scheme.
464          If present, it is a quoted string of one or more tokens indicating
465          the "quality of protection" values supported by the server. The
466          value "auth" indicates authentication; the value "auth-int"
467          indicates authentication with integrity protection; see the
468          descriptions below for calculating the response directive value for
469         the application of this choice. Unrecognized options MUST be
470         ignored.
471      * @return
472      */

473     private static String JavaDoc getQop() {
474         return "auth,auth-int";
475     }
476
477     /**
478      * A string of data, specified by the server, which should be returned
479          by the client unchanged in the Authorization header of subsequent
480          requests with URIs in the same protection space. It is recommended
481          that this string be base64 or hexadecimal data.
482      * @return
483      */

484     private static String JavaDoc getOpaque() {
485         return "5ccc069c403ebaf9f0171e9517f40e41";
486     }
487
488     /**
489      *
490      A string indicating a pair of algorithms used to produce the digest
491      and a checksum. If this is not present it is assumed to be "MD5".
492      If the algorithm is not understood, the challenge should be ignored
493      (and a different one used, if there is more than one).
494
495      In this document the string obtained by applying the digest
496      algorithm to the data "data" with secret "secret" will be denoted
497      by KD(secret, data), and the string obtained by applying the
498      checksum algorithm to the data "data" will be denoted H(data). The
499      notation unq(X) means the value of the quoted-string X without the
500      surrounding quotes.
501
502      For the "MD5" and "MD5-sess" algorithms
503
504          H(data) = MD5(data)
505
506      and
507
508          KD(secret, data) = H(concat(secret, ":", data))
509
510      i.e., the digest is the MD5 of the secret concatenated with a colon
511      concatenated with the data. The "MD5-sess" algorithm is intended to
512      allow efficient 3rd party authentication servers; for the
513      difference in usage, see the description in section 3.2.2.2.
514      * @return
515      */

516     private static String JavaDoc getAlgorithm() {
517         return "MD5";
518     }
519
520     /**
521      * //nonce
522             
523        A server-specified data string which should be uniquely generated
524          each time a 401 response is made. It is recommended that this
525          string be base64 or hexadecimal data. Specifically, since the
526          string is passed in the header lines as a quoted string, the
527          double-quote character is not allowed.
528
529          The contents of the nonce are implementation dependent. The quality
530          of the implementation depends on a good choice. A nonce might, for
531          example, be constructed as the base 64 encoding of
532
533              time-stamp H(time-stamp ":" ETag ":" private-key)
534
535          where time-stamp is a server-generated time or other non-repeating
536          value, ETag is the value of the HTTP ETag header associated with
537          the requested entity, and private-key is data known only to the
538          server. With a nonce of this form a server would recalculate the
539          hash portion after receiving the client authentication header and
540          reject the request if it did not match the nonce from that header
541          or if the time-stamp value is not recent enough. In this way the
542          server can limit the time of the nonce's validity. The inclusion of
543          the ETag prevents a replay request for an updated version of the
544          resource. (Note: including the IP address of the client in the
545          nonce would appear to offer the server the ability to limit the
546          reuse of the nonce to the same client that originally got it.
547          However, that would break proxy farms, where requests from a single
548          user often go through different proxies in the farm. Also, IP
549          address spoofing is not that hard.)
550
551          An implementation might choose not to accept a previously used
552          nonce or a previously used digest, in order to protect against a
553          replay attack. Or, an implementation might choose to use one-time
554          nonces or digests for POST or PUT requests and a time-stamp for GET
555          requests. For more details on the issues involved see section 4.
556          of this document. The nonce is opaque to the client.
557      * @param request
558      * @return
559      */

560     private static String JavaDoc getNonce(HttpServletRequest JavaDoc request){
561         return "dcd98b7102dd2f0e8b11d0f600bfb0c093";
562     }
563
564
565     /**
566      * gets the HttpRequest password field
567      * @return password field
568      */

569     public static String JavaDoc getPasswordField() {
570         return passwordField;
571     }
572
573     public static void setPasswordField(String JavaDoc passwordField) {
574         if(passwordField!=null){
575             HttpCallbackHandler.passwordField = passwordField;
576         }
577     }
578
579     /**
580      * gets the HttpRequest login field
581      * @return login field
582      */

583     public static String JavaDoc getLoginField() {
584         return loginField;
585     }
586
587     public static void setLoginField(String JavaDoc loginField) {
588         if(loginField!=null){
589             HttpCallbackHandler.loginField = loginField;
590         }
591     }
592
593     public void setAfterRegistration(boolean afterRegistration) {
594        this.afterRegistration = afterRegistration;
595     }
596
597     public boolean isAfterRegistration() {
598         return afterRegistration;
599     }
600 }
601
Popular Tags