001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.ui.digestauth;
017:
018: import java.io.IOException;
019:
020: import javax.servlet.ServletException;
021: import javax.servlet.ServletRequest;
022: import javax.servlet.ServletResponse;
023: import javax.servlet.http.HttpServletResponse;
024:
025: import org.acegisecurity.AuthenticationException;
026: import org.acegisecurity.ui.AuthenticationEntryPoint;
027: import org.apache.commons.codec.binary.Base64;
028: import org.apache.commons.codec.digest.DigestUtils;
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.springframework.beans.factory.InitializingBean;
032: import org.springframework.core.Ordered;
033:
034: /**
035: * Used by the <code>SecurityEnforcementFilter</code> to commence authentication via the {@link
036: * DigestProcessingFilter}.<p>The nonce sent back to the user agent will be valid for the period indicated by
037: * {@link #setNonceValiditySeconds(int)}. By default this is 300 seconds. Shorter times should be used if replay
038: * attacks are a major concern. Larger values can be used if performance is a greater concern. This class correctly
039: * presents the <code>stale=true</code> header when the nonce has expierd, so properly implemented user agents will
040: * automatically renegotiate with a new nonce value (ie without presenting a new password dialog box to the user).</p>
041: *
042: * @author Ben Alex
043: * @version $Id: DigestProcessingFilterEntryPoint.java 1822 2007-05-17 12:20:16Z vishalpuri $
044: */
045: public class DigestProcessingFilterEntryPoint implements
046: AuthenticationEntryPoint, InitializingBean, Ordered {
047: //~ Static fields/initializers =====================================================================================
048:
049: private static final Log logger = LogFactory
050: .getLog(DigestProcessingFilterEntryPoint.class);
051:
052: //~ Instance fields ================================================================================================
053:
054: private String key;
055: private String realmName;
056: private int nonceValiditySeconds = 300;
057: private int order = Integer.MAX_VALUE; // ~ default
058:
059: //~ Methods ========================================================================================================
060:
061: public int getOrder() {
062: return order;
063: }
064:
065: public void setOrder(int order) {
066: this .order = order;
067: }
068:
069: public void afterPropertiesSet() throws Exception {
070: if ((realmName == null) || "".equals(realmName)) {
071: throw new IllegalArgumentException(
072: "realmName must be specified");
073: }
074:
075: if ((key == null) || "".equals(key)) {
076: throw new IllegalArgumentException("key must be specified");
077: }
078: }
079:
080: public void commence(ServletRequest request,
081: ServletResponse response,
082: AuthenticationException authException) throws IOException,
083: ServletException {
084: HttpServletResponse httpResponse = (HttpServletResponse) response;
085:
086: // compute a nonce (do not use remote IP address due to proxy farms)
087: // format of nonce is:
088: // base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
089: long expiryTime = System.currentTimeMillis()
090: + (nonceValiditySeconds * 1000);
091: String signatureValue = new String(DigestUtils
092: .md5Hex(expiryTime + ":" + key));
093: String nonceValue = expiryTime + ":" + signatureValue;
094: String nonceValueBase64 = new String(Base64
095: .encodeBase64(nonceValue.getBytes()));
096:
097: // qop is quality of protection, as defined by RFC 2617.
098: // we do not use opaque due to IE violation of RFC 2617 in not
099: // representing opaque on subsequent requests in same session.
100: String authenticateHeader = "Digest realm=\"" + realmName
101: + "\", " + "qop=\"auth\", nonce=\"" + nonceValueBase64
102: + "\"";
103:
104: if (authException instanceof NonceExpiredException) {
105: authenticateHeader = authenticateHeader
106: + ", stale=\"true\"";
107: }
108:
109: if (logger.isDebugEnabled()) {
110: logger.debug("WWW-Authenticate header sent to user agent: "
111: + authenticateHeader);
112: }
113:
114: httpResponse.addHeader("WWW-Authenticate", authenticateHeader);
115: httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
116: authException.getMessage());
117: }
118:
119: public String getKey() {
120: return key;
121: }
122:
123: public int getNonceValiditySeconds() {
124: return nonceValiditySeconds;
125: }
126:
127: public String getRealmName() {
128: return realmName;
129: }
130:
131: public void setKey(String key) {
132: this .key = key;
133: }
134:
135: public void setNonceValiditySeconds(int nonceValiditySeconds) {
136: this .nonceValiditySeconds = nonceValiditySeconds;
137: }
138:
139: public void setRealmName(String realmName) {
140: this.realmName = realmName;
141: }
142: }
|