1
2
3
4
5
6
7
8
9
10
11
12
13
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
57
58 private static final Log logger = LogFactory.getLog(DigestProcessingFilterEntryPoint.class);
59
60
61
62 private String key;
63 private String realmName;
64 private int nonceValiditySeconds = 300;
65
66
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
108
109
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
119
120
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 }