View Javadoc

    1   /* Copyright 2004, 2005 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 org.acegisecurity.AuthenticationException;
    19  import org.acegisecurity.intercept.web.AuthenticationEntryPoint;
    20
    21  import org.apache.commons.codec.binary.Base64;
    22  import org.apache.commons.codec.digest.DigestUtils;
    23  import org.apache.commons.logging.Log;
    24  import org.apache.commons.logging.LogFactory;
    25
    26  import org.springframework.beans.factory.InitializingBean;
    27
    28  import java.io.IOException;
    29
    30  import javax.servlet.ServletException;
    31  import javax.servlet.ServletRequest;
    32  import javax.servlet.ServletResponse;
    33  import javax.servlet.http.HttpServletResponse;
    34
    35
    36  /***
    37   * Used by the <code>SecurityEnforcementFilter</code> to commence
    38   * authentication via the {@link DigestProcessingFilter}.
    39   *
    40   * <p>
    41   * The nonce sent back to the user agent will be valid for the period indicated
    42   * by {@link #setNonceValiditySeconds(int)}. By default this is 300 seconds.
    43   * Shorter times should be used if replay attacks are a major concern. Larger
    44   * values can be used if performance is a greater concern. This class
    45   * correctly presents the <code>stale=true</code> header when the nonce has
    46   * expierd, so properly implemented user agents will automatically renegotiate
    47   * with a new nonce value (ie without presenting a new password dialog box to
    48   * the user).
    49   * </p>
    50   *
    51   * @author Ben Alex
    52   * @version $Id: DigestProcessingFilterEntryPoint.java,v 1.2 2005/11/17 00:56:10 benalex Exp $
    53   */
    54  public class DigestProcessingFilterEntryPoint
    55      implements AuthenticationEntryPoint, InitializingBean {
    56      //~ Static fields/initializers =============================================
    57
    58      private static final Log logger = LogFactory.getLog(DigestProcessingFilterEntryPoint.class);
    59
    60      //~ Instance fields ========================================================
    61
    62      private String key;
    63      private String realmName;
    64      private int nonceValiditySeconds = 300;
    65
    66      //~ Methods ================================================================
    67
    68      public void setKey(String key) {
    69          this.key = key;
    70      }
    71
    72      public String getKey() {
    73          return key;
    74      }
    75
    76      public void setNonceValiditySeconds(int nonceValiditySeconds) {
    77          this.nonceValiditySeconds = nonceValiditySeconds;
    78      }
    79
    80      public int getNonceValiditySeconds() {
    81          return nonceValiditySeconds;
    82      }
    83
    84      public void setRealmName(String realmName) {
    85          this.realmName = realmName;
    86      }
    87
    88      public String getRealmName() {
    89          return realmName;
    90      }
    91
    92      public void afterPropertiesSet() throws Exception {
    93          if ((realmName == null) || "".equals(realmName)) {
    94              throw new IllegalArgumentException("realmName must be specified");
    95          }
    96
    97          if ((key == null) || "".equals(key)) {
    98              throw new IllegalArgumentException("key must be specified");
    99          }
    100     }
    101
    102     public void commence(ServletRequest request, ServletResponse response,
    103         AuthenticationException authException)
    104         throws IOException, ServletException {
    105         HttpServletResponse httpResponse = (HttpServletResponse) response;
    106
    107         // compute a nonce (do not use remote IP address due to proxy farms)
    108         // format of nonce is:
    109         //   base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
    110         long expiryTime = System.currentTimeMillis()
    111             + (nonceValiditySeconds * 1000);
    112         String signatureValue = new String(DigestUtils.md5Hex(expiryTime + ":"
    113                     + key));
    114         String nonceValue = expiryTime + ":" + signatureValue;
    115         String nonceValueBase64 = new String(Base64.encodeBase64(
    116                     nonceValue.getBytes()));
    117
    118         // qop is quality of protection, as defined by RFC 2617.
    119         // we do not use opaque due to IE violation of RFC 2617 in not
    120         // representing opaque on subsequent requests in same session.
    121         String authenticateHeader = "Digest realm=\"" + realmName + "\", "
    122             + "qop=\"auth\", nonce=\"" + nonceValueBase64 + "\"";
    123
    124         if (authException instanceof NonceExpiredException) {
    125             authenticateHeader = authenticateHeader + ", stale=\"true\"";
    126         }
    127
    128         if (logger.isDebugEnabled()) {
    129             logger.debug("WWW-Authenticate header sent to user agent: "
    130                 + authenticateHeader);
    131         }
    132
    133         httpResponse.addHeader("WWW-Authenticate", authenticateHeader);
    134         httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
    135             authException.getMessage());
    136     }
    137 }