View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.ui.digestauth;
17  
18  import java.io.IOException;
19  
20  import javax.servlet.ServletException;
21  import javax.servlet.ServletRequest;
22  import javax.servlet.ServletResponse;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.acegisecurity.AuthenticationException;
26  import org.acegisecurity.ui.AuthenticationEntryPoint;
27  import org.apache.commons.codec.binary.Base64;
28  import org.apache.commons.codec.digest.DigestUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.springframework.beans.factory.InitializingBean;
32  import org.springframework.core.Ordered;
33  
34  
35  /**
36   * Used by the <code>SecurityEnforcementFilter</code> to commence authentication via the {@link
37   * DigestProcessingFilter}.<p>The nonce sent back to the user agent will be valid for the period indicated by
38   * {@link #setNonceValiditySeconds(int)}. By default this is 300 seconds. Shorter times should be used if replay
39   * attacks are a major concern. Larger values can be used if performance is a greater concern. This class correctly
40   * presents the <code>stale=true</code> header when the nonce has expierd, so properly implemented user agents will
41   * automatically renegotiate with a new nonce value (ie without presenting a new password dialog box to the user).</p>
42   *
43   * @author Ben Alex
44   * @version $Id: DigestProcessingFilterEntryPoint.java 1822 2007-05-17 12:20:16Z vishalpuri $
45   */
46  public class DigestProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean, Ordered {
47      //~ Static fields/initializers =====================================================================================
48  
49      private static final Log logger = LogFactory.getLog(DigestProcessingFilterEntryPoint.class);
50  
51      //~ Instance fields ================================================================================================
52  
53      private String key;
54      private String realmName;
55      private int nonceValiditySeconds = 300;
56      private int order = Integer.MAX_VALUE; // ~ default
57  
58      //~ Methods ========================================================================================================
59  
60      public int getOrder() {
61  		return order;
62  	}
63  
64  	public void setOrder(int order) {
65  		this.order = order;
66  	}
67  
68  	public void afterPropertiesSet() throws Exception {
69          if ((realmName == null) || "".equals(realmName)) {
70              throw new IllegalArgumentException("realmName must be specified");
71          }
72  
73          if ((key == null) || "".equals(key)) {
74              throw new IllegalArgumentException("key must be specified");
75          }
76      }
77  
78      public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
79          throws IOException, ServletException {
80          HttpServletResponse httpResponse = (HttpServletResponse) response;
81  
82          // compute a nonce (do not use remote IP address due to proxy farms)
83          // format of nonce is:  
84          //   base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
85          long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds * 1000);
86          String signatureValue = new String(DigestUtils.md5Hex(expiryTime + ":" + key));
87          String nonceValue = expiryTime + ":" + signatureValue;
88          String nonceValueBase64 = new String(Base64.encodeBase64(nonceValue.getBytes()));
89  
90          // qop is quality of protection, as defined by RFC 2617.
91          // we do not use opaque due to IE violation of RFC 2617 in not
92          // representing opaque on subsequent requests in same session.
93          String authenticateHeader = "Digest realm=\"" + realmName + "\", " + "qop=\"auth\", nonce=\""
94              + nonceValueBase64 + "\"";
95  
96          if (authException instanceof NonceExpiredException) {
97              authenticateHeader = authenticateHeader + ", stale=\"true\"";
98          }
99  
100         if (logger.isDebugEnabled()) {
101             logger.debug("WWW-Authenticate header sent to user agent: " + authenticateHeader);
102         }
103 
104         httpResponse.addHeader("WWW-Authenticate", authenticateHeader);
105         httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
106     }
107 
108     public String getKey() {
109         return key;
110     }
111 
112     public int getNonceValiditySeconds() {
113         return nonceValiditySeconds;
114     }
115 
116     public String getRealmName() {
117         return realmName;
118     }
119 
120     public void setKey(String key) {
121         this.key = key;
122     }
123 
124     public void setNonceValiditySeconds(int nonceValiditySeconds) {
125         this.nonceValiditySeconds = nonceValiditySeconds;
126     }
127 
128     public void setRealmName(String realmName) {
129         this.realmName = realmName;
130     }
131 }