mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-18 10:02:14 +00:00
SEC-3108: DigestAuthenticationFilter should use SecurityContextHolder.createEmptyContext()
This commit is contained in:
parent
90f230cbfa
commit
c64b80564e
@ -38,6 +38,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||||
import org.springframework.security.crypto.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserCache;
|
import org.springframework.security.core.userdetails.UserCache;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@ -49,46 +50,52 @@ import org.springframework.util.Assert;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
import org.springframework.web.filter.GenericFilterBean;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a HTTP request's Digest authorization headers, putting the result into the
|
* Processes a HTTP request's Digest authorization headers, putting the result into the
|
||||||
* <code>SecurityContextHolder</code>.
|
* <code>SecurityContextHolder</code>.
|
||||||
* <p>
|
* <p>
|
||||||
* For a detailed background on what this filter is designed to process, refer to
|
* For a detailed background on what this filter is designed to process, refer to <a
|
||||||
* <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a> (which superseded RFC 2069, although this
|
* href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a> (which superseded RFC 2069,
|
||||||
* filter support clients that implement either RFC 2617 or RFC 2069).
|
* although this filter support clients that implement either RFC 2617 or RFC 2069).
|
||||||
* <p>
|
* <p>
|
||||||
* This filter can be used to provide Digest authentication services to both remoting protocol clients (such as
|
* This filter can be used to provide Digest authentication services to both remoting
|
||||||
* Hessian and SOAP) as well as standard user agents (such as Internet Explorer and FireFox).
|
* protocol clients (such as Hessian and SOAP) as well as standard user agents (such as
|
||||||
|
* Internet Explorer and FireFox).
|
||||||
* <p>
|
* <p>
|
||||||
* This Digest implementation has been designed to avoid needing to store session state between invocations.
|
* This Digest implementation has been designed to avoid needing to store session state
|
||||||
* All session management information is stored in the "nonce" that is sent to the client by the {@link
|
* between invocations. All session management information is stored in the "nonce" that
|
||||||
* DigestAuthenticationEntryPoint}.
|
* is sent to the client by the {@link DigestAuthenticationEntryPoint}.
|
||||||
* <p>
|
* <p>
|
||||||
* If authentication is successful, the resulting {@link org.springframework.security.core.Authentication Authentication}
|
* If authentication is successful, the resulting
|
||||||
* object will be placed into the <code>SecurityContextHolder</code>.
|
* {@link org.springframework.security.core.Authentication Authentication} object will be
|
||||||
|
* placed into the <code>SecurityContextHolder</code>.
|
||||||
* <p>
|
* <p>
|
||||||
* If authentication fails, an {@link org.springframework.security.web.AuthenticationEntryPoint AuthenticationEntryPoint}
|
* If authentication fails, an
|
||||||
* implementation is called. This must always be {@link DigestAuthenticationEntryPoint}, which will prompt the user
|
* {@link org.springframework.security.web.AuthenticationEntryPoint
|
||||||
* to authenticate again via Digest authentication.
|
* AuthenticationEntryPoint} implementation is called. This must always be
|
||||||
|
* {@link DigestAuthenticationEntryPoint}, which will prompt the user to authenticate
|
||||||
|
* again via Digest authentication.
|
||||||
* <p>
|
* <p>
|
||||||
* Note there are limitations to Digest authentication, although it is a more comprehensive and secure solution
|
* Note there are limitations to Digest authentication, although it is a more
|
||||||
* than Basic authentication. Please see RFC 2617 section 4 for a full discussion on the advantages of Digest
|
* comprehensive and secure solution than Basic authentication. Please see RFC 2617
|
||||||
* authentication over Basic authentication, including commentary on the limitations that it still imposes.
|
* section 4 for a full discussion on the advantages of Digest authentication over Basic
|
||||||
|
* authentication, including commentary on the limitations that it still imposes.
|
||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @author Luke Taylor
|
* @author Luke Taylor
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class DigestAuthenticationFilter extends GenericFilterBean implements MessageSourceAware {
|
public class DigestAuthenticationFilter extends GenericFilterBean implements
|
||||||
//~ Static fields/initializers =====================================================================================
|
MessageSourceAware {
|
||||||
|
// ~ Static fields/initializers
|
||||||
|
// =====================================================================================
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(DigestAuthenticationFilter.class);
|
private static final Log logger = LogFactory.getLog(DigestAuthenticationFilter.class);
|
||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
// ~ Instance fields
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
private AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
|
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
|
||||||
private DigestAuthenticationEntryPoint authenticationEntryPoint;
|
private DigestAuthenticationEntryPoint authenticationEntryPoint;
|
||||||
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||||
private UserCache userCache = new NullUserCache();
|
private UserCache userCache = new NullUserCache();
|
||||||
@ -96,12 +103,14 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
private boolean passwordAlreadyEncoded = false;
|
private boolean passwordAlreadyEncoded = false;
|
||||||
private boolean createAuthenticatedToken = false;
|
private boolean createAuthenticatedToken = false;
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
// ~ Methods
|
||||||
|
// ========================================================================================================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
Assert.notNull(userDetailsService, "A UserDetailsService is required");
|
Assert.notNull(userDetailsService, "A UserDetailsService is required");
|
||||||
Assert.notNull(authenticationEntryPoint, "A DigestAuthenticationEntryPoint is required");
|
Assert.notNull(authenticationEntryPoint,
|
||||||
|
"A DigestAuthenticationEntryPoint is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||||
@ -118,14 +127,17 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Digest Authorization header received from user agent: " + header);
|
logger.debug("Digest Authorization header received from user agent: "
|
||||||
|
+ header);
|
||||||
}
|
}
|
||||||
|
|
||||||
DigestData digestAuth = new DigestData(header);
|
DigestData digestAuth = new DigestData(header);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
digestAuth.validateAndDecode(authenticationEntryPoint.getKey(), authenticationEntryPoint.getRealmName());
|
digestAuth.validateAndDecode(authenticationEntryPoint.getKey(),
|
||||||
} catch (BadCredentialsException e) {
|
authenticationEntryPoint.getRealmName());
|
||||||
|
}
|
||||||
|
catch (BadCredentialsException e) {
|
||||||
fail(request, response, e);
|
fail(request, response, e);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -151,38 +163,45 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
userCache.putUserInCache(user);
|
userCache.putUserInCache(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
|
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(),
|
||||||
|
request.getMethod());
|
||||||
|
|
||||||
// If digest is incorrect, try refreshing from backend and recomputing
|
// If digest is incorrect, try refreshing from backend and recomputing
|
||||||
if (!serverDigestMd5.equals(digestAuth.getResponse()) && cacheWasUsed) {
|
if (!serverDigestMd5.equals(digestAuth.getResponse()) && cacheWasUsed) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug(
|
logger.debug("Digest comparison failure; trying to refresh user from DAO in case password had changed");
|
||||||
"Digest comparison failure; trying to refresh user from DAO in case password had changed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user = userDetailsService.loadUserByUsername(digestAuth.getUsername());
|
user = userDetailsService.loadUserByUsername(digestAuth.getUsername());
|
||||||
userCache.putUserInCache(user);
|
userCache.putUserInCache(user);
|
||||||
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
|
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(),
|
||||||
|
request.getMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (UsernameNotFoundException notFound) {
|
}
|
||||||
fail(request, response,
|
catch (UsernameNotFoundException notFound) {
|
||||||
new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.usernameNotFound",
|
fail(request,
|
||||||
new Object[]{digestAuth.getUsername()}, "Username {0} not found")));
|
response,
|
||||||
|
new BadCredentialsException(messages.getMessage(
|
||||||
|
"DigestAuthenticationFilter.usernameNotFound",
|
||||||
|
new Object[] { digestAuth.getUsername() },
|
||||||
|
"Username {0} not found")));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If digest is still incorrect, definitely reject authentication attempt
|
// If digest is still incorrect, definitely reject authentication attempt
|
||||||
if (!serverDigestMd5.equals(digestAuth.getResponse())) {
|
if (!serverDigestMd5.equals(digestAuth.getResponse())) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Expected response: '" + serverDigestMd5 + "' but received: '" + digestAuth.getResponse()
|
logger.debug("Expected response: '" + serverDigestMd5
|
||||||
|
+ "' but received: '" + digestAuth.getResponse()
|
||||||
+ "'; is AuthenticationDao returning clear text passwords?");
|
+ "'; is AuthenticationDao returning clear text passwords?");
|
||||||
}
|
}
|
||||||
|
|
||||||
fail(request, response,
|
fail(request,
|
||||||
new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.incorrectResponse",
|
response,
|
||||||
|
new BadCredentialsException(messages.getMessage(
|
||||||
|
"DigestAuthenticationFilter.incorrectResponse",
|
||||||
"Incorrect response")));
|
"Incorrect response")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -192,8 +211,10 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
// We do this last so we can direct the user agent its nonce is stale
|
// We do this last so we can direct the user agent its nonce is stale
|
||||||
// but the request was otherwise appearing to be valid
|
// but the request was otherwise appearing to be valid
|
||||||
if (digestAuth.isNonceExpired()) {
|
if (digestAuth.isNonceExpired()) {
|
||||||
fail(request, response,
|
fail(request,
|
||||||
new NonceExpiredException(messages.getMessage("DigestAuthenticationFilter.nonceExpired",
|
response,
|
||||||
|
new NonceExpiredException(messages.getMessage(
|
||||||
|
"DigestAuthenticationFilter.nonceExpired",
|
||||||
"Nonce has expired/timed out")));
|
"Nonce has expired/timed out")));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -204,27 +225,34 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
+ "' with response: '" + digestAuth.getResponse() + "'");
|
+ "' with response: '" + digestAuth.getResponse() + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(createSuccessfulAuthentication(request, user));
|
Authentication authentication = createSuccessfulAuthentication(request, user);
|
||||||
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
|
context.setAuthentication(authentication);
|
||||||
|
SecurityContextHolder.setContext(context);
|
||||||
|
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) {
|
private Authentication createSuccessfulAuthentication(HttpServletRequest request,
|
||||||
|
UserDetails user) {
|
||||||
UsernamePasswordAuthenticationToken authRequest;
|
UsernamePasswordAuthenticationToken authRequest;
|
||||||
if (createAuthenticatedToken) {
|
if (createAuthenticatedToken) {
|
||||||
authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
|
authRequest = new UsernamePasswordAuthenticationToken(user,
|
||||||
|
user.getPassword(), user.getAuthorities());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword());
|
authRequest = new UsernamePasswordAuthenticationToken(user,
|
||||||
|
user.getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
|
authRequest.setDetails(authenticationDetailsSource
|
||||||
|
.buildDetails((HttpServletRequest) request));
|
||||||
|
|
||||||
return authRequest;
|
return authRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fail(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
|
private void fail(HttpServletRequest request, HttpServletResponse response,
|
||||||
throws IOException, ServletException {
|
AuthenticationException failed) throws IOException, ServletException {
|
||||||
SecurityContextHolder.getContext().setAuthentication(null);
|
SecurityContextHolder.getContext().setAuthentication(null);
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
@ -246,12 +274,15 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
return userDetailsService;
|
return userDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
|
public void setAuthenticationDetailsSource(
|
||||||
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
|
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
|
||||||
|
Assert.notNull(authenticationDetailsSource,
|
||||||
|
"AuthenticationDetailsSource required");
|
||||||
this.authenticationDetailsSource = authenticationDetailsSource;
|
this.authenticationDetailsSource = authenticationDetailsSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationEntryPoint(DigestAuthenticationEntryPoint authenticationEntryPoint) {
|
public void setAuthenticationEntryPoint(
|
||||||
|
DigestAuthenticationEntryPoint authenticationEntryPoint) {
|
||||||
this.authenticationEntryPoint = authenticationEntryPoint;
|
this.authenticationEntryPoint = authenticationEntryPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,18 +302,16 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
this.userDetailsService = userDetailsService;
|
this.userDetailsService = userDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you set this property, the Authentication object, which is
|
* If you set this property, the Authentication object, which is created after the
|
||||||
* created after the successful digest authentication will be marked
|
* successful digest authentication will be marked as <b>authenticated</b> and filled
|
||||||
* as <b>authenticated</b> and filled with the authorities loaded by
|
* with the authorities loaded by the UserDetailsService. It therefore will not be
|
||||||
* the UserDetailsService. It therefore will not be re-authenticated
|
* re-authenticated by your AuthenticationProvider. This means, that only the password
|
||||||
* by your AuthenticationProvider. This means, that only the password
|
|
||||||
* of the user is checked, but not the flags like isEnabled() or
|
* of the user is checked, but not the flags like isEnabled() or
|
||||||
* isAccountNonExpired(). You will save some time by enabling this flag,
|
* isAccountNonExpired(). You will save some time by enabling this flag, as otherwise
|
||||||
* as otherwise your UserDetailsService will be called twice. A more secure
|
* your UserDetailsService will be called twice. A more secure option would be to
|
||||||
* option would be to introduce a cache around your UserDetailsService, but
|
* introduce a cache around your UserDetailsService, but if you don't use these flags,
|
||||||
* if you don't use these flags, you can also safely enable this option.
|
* you can also safely enable this option.
|
||||||
*
|
*
|
||||||
* @param createAuthenticatedToken default is false
|
* @param createAuthenticatedToken default is false
|
||||||
*/
|
*/
|
||||||
@ -304,8 +333,10 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
|
|
||||||
DigestData(String header) {
|
DigestData(String header) {
|
||||||
section212response = header.substring(7);
|
section212response = header.substring(7);
|
||||||
String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(section212response, ',');
|
String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(
|
||||||
Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
|
section212response, ',');
|
||||||
|
Map<String, String> headerMap = DigestAuthUtils
|
||||||
|
.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
|
||||||
|
|
||||||
username = headerMap.get("username");
|
username = headerMap.get("username");
|
||||||
realm = headerMap.get("realm");
|
realm = headerMap.get("realm");
|
||||||
@ -317,69 +348,87 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
cnonce = headerMap.get("cnonce"); // RFC 2617 extension
|
cnonce = headerMap.get("cnonce"); // RFC 2617 extension
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Extracted username: '" + username + "'; realm: '" + realm + "'; nonce: '"
|
logger.debug("Extracted username: '" + username + "'; realm: '" + realm
|
||||||
+ nonce + "'; uri: '" + uri + "'; response: '" + response + "'");
|
+ "'; nonce: '" + nonce + "'; uri: '" + uri + "'; response: '"
|
||||||
|
+ response + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateAndDecode(String entryPointKey, String expectedRealm) throws BadCredentialsException {
|
void validateAndDecode(String entryPointKey, String expectedRealm)
|
||||||
|
throws BadCredentialsException {
|
||||||
// Check all required parameters were supplied (ie RFC 2069)
|
// Check all required parameters were supplied (ie RFC 2069)
|
||||||
if ((username == null) || (realm == null) || (nonce == null) || (uri == null) || (response == null)) {
|
if ((username == null) || (realm == null) || (nonce == null) || (uri == null)
|
||||||
throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.missingMandatory",
|
|| (response == null)) {
|
||||||
new Object[]{section212response}, "Missing mandatory digest value; received header {0}"));
|
throw new BadCredentialsException(messages.getMessage(
|
||||||
|
"DigestAuthenticationFilter.missingMandatory",
|
||||||
|
new Object[] { section212response },
|
||||||
|
"Missing mandatory digest value; received header {0}"));
|
||||||
}
|
}
|
||||||
// Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
|
// Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
|
||||||
if ("auth".equals(qop)) {
|
if ("auth".equals(qop)) {
|
||||||
if ((nc == null) || (cnonce == null)) {
|
if ((nc == null) || (cnonce == null)) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("extracted nc: '" + nc + "'; cnonce: '" + cnonce + "'");
|
logger.debug("extracted nc: '" + nc + "'; cnonce: '" + cnonce
|
||||||
|
+ "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.missingAuth",
|
throw new BadCredentialsException(messages.getMessage(
|
||||||
new Object[]{section212response}, "Missing mandatory digest value; received header {0}"));
|
"DigestAuthenticationFilter.missingAuth",
|
||||||
|
new Object[] { section212response },
|
||||||
|
"Missing mandatory digest value; received header {0}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check realm name equals what we expected
|
// Check realm name equals what we expected
|
||||||
if (!expectedRealm.equals(realm)) {
|
if (!expectedRealm.equals(realm)) {
|
||||||
throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.incorrectRealm",
|
throw new BadCredentialsException(
|
||||||
new Object[]{realm, expectedRealm},
|
messages.getMessage("DigestAuthenticationFilter.incorrectRealm",
|
||||||
"Response realm name '{0}' does not match system realm name of '{1}'"));
|
new Object[] { realm, expectedRealm },
|
||||||
|
"Response realm name '{0}' does not match system realm name of '{1}'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint)
|
// Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint)
|
||||||
if (!Base64.isBase64(nonce.getBytes())) {
|
if (!Base64.isBase64(nonce.getBytes())) {
|
||||||
throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceEncoding",
|
throw new BadCredentialsException(messages.getMessage(
|
||||||
new Object[]{nonce}, "Nonce is not encoded in Base64; received nonce {0}"));
|
"DigestAuthenticationFilter.nonceEncoding",
|
||||||
|
new Object[] { nonce },
|
||||||
|
"Nonce is not encoded in Base64; received nonce {0}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode nonce from Base64
|
// Decode nonce from Base64
|
||||||
// format of nonce is:
|
// format of nonce is:
|
||||||
// base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
|
// base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
|
||||||
String nonceAsPlainText = new String(Base64.decode(nonce.getBytes()));
|
String nonceAsPlainText = new String(Base64.decode(nonce.getBytes()));
|
||||||
String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");
|
String[] nonceTokens = StringUtils.delimitedListToStringArray(
|
||||||
|
nonceAsPlainText, ":");
|
||||||
|
|
||||||
if (nonceTokens.length != 2) {
|
if (nonceTokens.length != 2) {
|
||||||
throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceNotTwoTokens",
|
throw new BadCredentialsException(messages.getMessage(
|
||||||
new Object[]{nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}"));
|
"DigestAuthenticationFilter.nonceNotTwoTokens",
|
||||||
|
new Object[] { nonceAsPlainText },
|
||||||
|
"Nonce should have yielded two tokens but was {0}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract expiry time from nonce
|
// Extract expiry time from nonce
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nonceExpiryTime = new Long(nonceTokens[0]).longValue();
|
nonceExpiryTime = new Long(nonceTokens[0]).longValue();
|
||||||
} catch (NumberFormatException nfe) {
|
}
|
||||||
throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceNotNumeric",
|
catch (NumberFormatException nfe) {
|
||||||
new Object[]{nonceAsPlainText},
|
throw new BadCredentialsException(
|
||||||
|
messages.getMessage("DigestAuthenticationFilter.nonceNotNumeric",
|
||||||
|
new Object[] { nonceAsPlainText },
|
||||||
"Nonce token should have yielded a numeric first token, but was {0}"));
|
"Nonce token should have yielded a numeric first token, but was {0}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check signature of nonce matches this expiry time
|
// Check signature of nonce matches this expiry time
|
||||||
String expectedNonceSignature = DigestAuthUtils.md5Hex(nonceExpiryTime + ":" + entryPointKey);
|
String expectedNonceSignature = DigestAuthUtils.md5Hex(nonceExpiryTime + ":"
|
||||||
|
+ entryPointKey);
|
||||||
|
|
||||||
if (!expectedNonceSignature.equals(nonceTokens[1])) {
|
if (!expectedNonceSignature.equals(nonceTokens[1])) {
|
||||||
new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceCompromised",
|
new BadCredentialsException(messages.getMessage(
|
||||||
new Object[]{nonceAsPlainText}, "Nonce token compromised {0}"));
|
"DigestAuthenticationFilter.nonceCompromised",
|
||||||
|
new Object[] { nonceAsPlainText }, "Nonce token compromised {0}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,8 +436,8 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
|
|||||||
// Compute the expected response-digest (will be in hex form)
|
// Compute the expected response-digest (will be in hex form)
|
||||||
|
|
||||||
// Don't catch IllegalArgumentException (already checked validity)
|
// Don't catch IllegalArgumentException (already checked validity)
|
||||||
return DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username, realm, password,
|
return DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username,
|
||||||
httpMethod, uri, qop, nonce, nc, cnonce);
|
realm, password, httpMethod, uri, qop, nonce, nc, cnonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isNonceExpired() {
|
boolean isNonceExpired() {
|
||||||
|
@ -15,11 +15,20 @@
|
|||||||
|
|
||||||
package org.springframework.security.web.authentication.www;
|
package org.springframework.security.web.authentication.www;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.fest.assertions.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
@ -32,7 +41,9 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@ -41,7 +52,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|||||||
import org.springframework.security.core.userdetails.cache.NullUserCache;
|
import org.springframework.security.core.userdetails.cache.NullUserCache;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DigestAuthenticationFilter}.
|
* Tests {@link DigestAuthenticationFilter}.
|
||||||
*
|
*
|
||||||
@ -49,7 +59,8 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author Luke Taylor
|
* @author Luke Taylor
|
||||||
*/
|
*/
|
||||||
public class DigestAuthenticationFilterTests {
|
public class DigestAuthenticationFilterTests {
|
||||||
//~ Static fields/initializers =====================================================================================
|
// ~ Static fields/initializers
|
||||||
|
// =====================================================================================
|
||||||
|
|
||||||
private static final String NC = "00000002";
|
private static final String NC = "00000002";
|
||||||
private static final String CNONCE = "c822c727a648aba7";
|
private static final String CNONCE = "c822c727a648aba7";
|
||||||
@ -65,23 +76,26 @@ public class DigestAuthenticationFilterTests {
|
|||||||
*/
|
*/
|
||||||
private static final String NONCE = generateNonce(60);
|
private static final String NONCE = generateNonce(60);
|
||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
// ~ Instance fields
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
// private ApplicationContext ctx;
|
// private ApplicationContext ctx;
|
||||||
private DigestAuthenticationFilter filter;
|
private DigestAuthenticationFilter filter;
|
||||||
private MockHttpServletRequest request;
|
private MockHttpServletRequest request;
|
||||||
|
|
||||||
|
// ~ Methods
|
||||||
|
// ========================================================================================================
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
private String createAuthorizationHeader(String username, String realm, String nonce,
|
||||||
|
String uri, String responseDigest, String qop, String nc, String cnonce) {
|
||||||
private String createAuthorizationHeader(String username, String realm, String nonce, String uri,
|
return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\""
|
||||||
String responseDigest, String qop, String nc, String cnonce) {
|
+ nonce + "\", uri=\"" + uri + "\", response=\"" + responseDigest
|
||||||
return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri
|
+ "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
|
||||||
+ "\", response=\"" + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter, final ServletRequest request,
|
private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter,
|
||||||
final boolean expectChainToProceed) throws ServletException, IOException {
|
final ServletRequest request, final boolean expectChainToProceed)
|
||||||
|
throws ServletException, IOException {
|
||||||
final MockHttpServletResponse response = new MockHttpServletResponse();
|
final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
final FilterChain chain = mock(FilterChain.class);
|
final FilterChain chain = mock(FilterChain.class);
|
||||||
@ -111,8 +125,10 @@ public class DigestAuthenticationFilterTests {
|
|||||||
|
|
||||||
// Create User Details Service
|
// Create User Details Service
|
||||||
UserDetailsService uds = new UserDetailsService() {
|
UserDetailsService uds = new UserDetailsService() {
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username)
|
||||||
return new User("rod,ok", "koala", AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"));
|
throws UsernameNotFoundException {
|
||||||
|
return new User("rod,ok", "koala", AuthorityUtils.createAuthorityList(
|
||||||
|
"ROLE_ONE", "ROLE_TWO"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -129,25 +145,28 @@ public class DigestAuthenticationFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExpiredNonceReturnsForbiddenWithStaleHeader()
|
public void testExpiredNonceReturnsForbiddenWithStaleHeader() throws Exception {
|
||||||
throws Exception {
|
|
||||||
String nonce = generateNonce(0);
|
String nonce = generateNonce(0);
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
REQUEST_URI, QOP, nonce, NC, CNONCE);
|
PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
Thread.sleep(1000); // ensures token expired
|
Thread.sleep(1000); // ensures token expired
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
|
|
||||||
String header = response.getHeader("WWW-Authenticate").toString().substring(7);
|
String header = response.getHeader("WWW-Authenticate").toString().substring(7);
|
||||||
String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
|
String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
|
||||||
Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
|
Map<String, String> headerMap = DigestAuthUtils
|
||||||
|
.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
|
||||||
assertEquals("true", headerMap.get("stale"));
|
assertEquals("true", headerMap.get("stale"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,13 +194,14 @@ public class DigestAuthenticationFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidDigestAuthorizationTokenGeneratesError()
|
public void testInvalidDigestAuthorizationTokenGeneratesError() throws Exception {
|
||||||
throws Exception {
|
|
||||||
String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
|
String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
|
||||||
|
|
||||||
request.addHeader("Authorization", "Digest " + new String(Base64.encodeBase64(token.getBytes())));
|
request.addHeader("Authorization",
|
||||||
|
"Digest " + new String(Base64.encodeBase64(token.getBytes())));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
@ -191,7 +211,8 @@ public class DigestAuthenticationFilterTests {
|
|||||||
public void testMalformedHeaderReturnsForbidden() throws Exception {
|
public void testMalformedHeaderReturnsForbidden() throws Exception {
|
||||||
request.addHeader("Authorization", "Digest scsdcsdc");
|
request.addHeader("Authorization", "Digest scsdcsdc");
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
@ -201,28 +222,36 @@ public class DigestAuthenticationFilterTests {
|
|||||||
public void testNonBase64EncodedNonceReturnsForbidden() throws Exception {
|
public void testNonBase64EncodedNonceReturnsForbidden() throws Exception {
|
||||||
String nonce = "NOT_BASE_64_ENCODED";
|
String nonce = "NOT_BASE_64_ENCODED";
|
||||||
|
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
REQUEST_URI, QOP, nonce, NC, CNONCE);
|
PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden() throws Exception {
|
public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden()
|
||||||
String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword".getBytes()));
|
throws Exception {
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword"
|
||||||
REQUEST_URI, QOP, nonce, NC, CNONCE);
|
.getBytes()));
|
||||||
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
|
PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
@ -230,29 +259,38 @@ public class DigestAuthenticationFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNonceWithNonNumericFirstElementReturnsForbidden() throws Exception {
|
public void testNonceWithNonNumericFirstElementReturnsForbidden() throws Exception {
|
||||||
String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement".getBytes()));
|
String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement"
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
.getBytes()));
|
||||||
REQUEST_URI, QOP, nonce, NC, CNONCE);
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
|
PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden() throws Exception {
|
public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden()
|
||||||
String nonce = new String(Base64.encodeBase64("a base 64 string without a colon".getBytes()));
|
throws Exception {
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
String nonce = new String(Base64.encodeBase64("a base 64 string without a colon"
|
||||||
REQUEST_URI, QOP, nonce, NC, CNONCE);
|
.getBytes()));
|
||||||
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
|
PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
@ -260,58 +298,67 @@ public class DigestAuthenticationFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNormalOperationWhenPasswordIsAlreadyEncoded() throws Exception {
|
public void testNormalOperationWhenPasswordIsAlreadyEncoded() throws Exception {
|
||||||
String encodedPassword = DigestAuthUtils.encodePasswordInA1Format(USERNAME, REALM, PASSWORD);
|
String encodedPassword = DigestAuthUtils.encodePasswordInA1Format(USERNAME,
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(true, USERNAME, REALM, encodedPassword, "GET",
|
REALM, PASSWORD);
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
String responseDigest = DigestAuthUtils.generateDigest(true, USERNAME, REALM,
|
||||||
|
encodedPassword, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
executeFilterInContainerSimulator(filter, request, true);
|
executeFilterInContainerSimulator(filter, request, true);
|
||||||
|
|
||||||
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(USERNAME,
|
assertEquals(USERNAME, ((UserDetails) SecurityContextHolder.getContext()
|
||||||
((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
|
.getAuthentication().getPrincipal()).getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNormalOperationWhenPasswordNotAlreadyEncoded() throws Exception {
|
public void testNormalOperationWhenPasswordNotAlreadyEncoded() throws Exception {
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
executeFilterInContainerSimulator(filter, request, true);
|
executeFilterInContainerSimulator(filter, request, true);
|
||||||
|
|
||||||
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(USERNAME,
|
assertEquals(USERNAME, ((UserDetails) SecurityContextHolder.getContext()
|
||||||
((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
|
.getAuthentication().getPrincipal()).getUsername());
|
||||||
assertFalse(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
|
assertFalse(SecurityContextHolder.getContext().getAuthentication()
|
||||||
|
.isAuthenticated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNormalOperationWhenPasswordNotAlreadyEncodedAndWithoutReAuthentication() throws Exception {
|
public void testNormalOperationWhenPasswordNotAlreadyEncodedAndWithoutReAuthentication()
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
throws Exception {
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
|
PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
filter.setCreateAuthenticatedToken(true);
|
filter.setCreateAuthenticatedToken(true);
|
||||||
executeFilterInContainerSimulator(filter, request, true);
|
executeFilterInContainerSimulator(filter, request, true);
|
||||||
|
|
||||||
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(USERNAME,
|
assertEquals(USERNAME, ((UserDetails) SecurityContextHolder.getContext()
|
||||||
((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
|
.getAuthentication().getPrincipal()).getUsername());
|
||||||
assertTrue(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
|
assertTrue(SecurityContextHolder.getContext().getAuthentication()
|
||||||
assertEquals(AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"),
|
.isAuthenticated());
|
||||||
|
assertEquals(AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"),
|
||||||
SecurityContextHolder.getContext().getAuthentication().getAuthorities());
|
SecurityContextHolder.getContext().getAuthentication().getAuthorities());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void otherAuthorizationSchemeIsIgnored()
|
public void otherAuthorizationSchemeIsIgnored() throws Exception {
|
||||||
throws Exception {
|
|
||||||
request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
|
request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
|
||||||
|
|
||||||
executeFilterInContainerSimulator(filter, request, true);
|
executeFilterInContainerSimulator(filter, request, true);
|
||||||
@ -319,14 +366,14 @@ public class DigestAuthenticationFilterTests {
|
|||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void startupDetectsMissingAuthenticationEntryPoint() throws Exception {
|
public void startupDetectsMissingAuthenticationEntryPoint() throws Exception {
|
||||||
DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
|
DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
|
||||||
filter.setUserDetailsService(mock(UserDetailsService.class));
|
filter.setUserDetailsService(mock(UserDetailsService.class));
|
||||||
filter.afterPropertiesSet();
|
filter.afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void startupDetectsMissingUserDetailsService() throws Exception {
|
public void startupDetectsMissingUserDetailsService() throws Exception {
|
||||||
DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
|
DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
|
||||||
filter.setAuthenticationEntryPoint(new DigestAuthenticationEntryPoint());
|
filter.setAuthenticationEntryPoint(new DigestAuthenticationEntryPoint());
|
||||||
@ -334,26 +381,32 @@ public class DigestAuthenticationFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void successfulLoginThenFailedLoginResultsInSessionLosingToken() throws Exception {
|
public void successfulLoginThenFailedLoginResultsInSessionLosingToken()
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
throws Exception {
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
|
PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
executeFilterInContainerSimulator(filter, request, true);
|
executeFilterInContainerSimulator(filter, request, true);
|
||||||
|
|
||||||
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
// Now retry, giving an invalid nonce
|
// Now retry, giving an invalid nonce
|
||||||
responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, "WRONG_PASSWORD", "GET",
|
responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
"WRONG_PASSWORD", "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request = new MockHttpServletRequest();
|
request = new MockHttpServletRequest();
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
// Check we lost our previous authentication
|
// Check we lost our previous authentication
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
@ -364,13 +417,16 @@ public class DigestAuthenticationFilterTests {
|
|||||||
public void wrongCnonceBasedOnDigestReturnsForbidden() throws Exception {
|
public void wrongCnonceBasedOnDigestReturnsForbidden() throws Exception {
|
||||||
String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
|
String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
|
||||||
|
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
|
PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, cnonce));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, cnonce));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
@ -379,13 +435,16 @@ public class DigestAuthenticationFilterTests {
|
|||||||
@Test
|
@Test
|
||||||
public void wrongDigestReturnsForbidden() throws Exception {
|
public void wrongDigestReturnsForbidden() throws Exception {
|
||||||
String password = "WRONG_PASSWORD";
|
String password = "WRONG_PASSWORD";
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, password, "GET",
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
password, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
@ -394,13 +453,16 @@ public class DigestAuthenticationFilterTests {
|
|||||||
@Test
|
@Test
|
||||||
public void wrongRealmReturnsForbidden() throws Exception {
|
public void wrongRealmReturnsForbidden() throws Exception {
|
||||||
String realm = "WRONG_REALM";
|
String realm = "WRONG_REALM";
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, realm, PASSWORD, "GET",
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, realm,
|
||||||
REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
@ -408,15 +470,41 @@ public class DigestAuthenticationFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void wrongUsernameReturnsForbidden() throws Exception {
|
public void wrongUsernameReturnsForbidden() throws Exception {
|
||||||
String responseDigest = DigestAuthUtils.generateDigest(false, "NOT_A_KNOWN_USER", REALM, PASSWORD,
|
String responseDigest = DigestAuthUtils.generateDigest(false, "NOT_A_KNOWN_USER",
|
||||||
"GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
REALM, PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
request.addHeader("Authorization",
|
request.addHeader(
|
||||||
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
|
MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
|
||||||
|
request, false);
|
||||||
|
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SEC-3108
|
||||||
|
@Test
|
||||||
|
public void authenticationCreatesEmptyContext() throws Exception {
|
||||||
|
SecurityContext existingContext = SecurityContextHolder.createEmptyContext();
|
||||||
|
TestingAuthenticationToken existingAuthentication = new TestingAuthenticationToken("existingauthenitcated", "pass", "ROLE_USER");
|
||||||
|
existingContext.setAuthentication(existingAuthentication);
|
||||||
|
|
||||||
|
SecurityContextHolder.setContext(existingContext);
|
||||||
|
|
||||||
|
String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
|
||||||
|
PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
|
||||||
|
|
||||||
|
request.addHeader(
|
||||||
|
"Authorization",
|
||||||
|
createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
|
||||||
|
responseDigest, QOP, NC, CNONCE));
|
||||||
|
|
||||||
|
filter.setCreateAuthenticatedToken(true);
|
||||||
|
executeFilterInContainerSimulator(filter, request, true);
|
||||||
|
|
||||||
|
assertThat(existingAuthentication).isSameAs(existingContext.getAuthentication());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user