Forráskód Böngészése

Remove dependency on commons-codec by using java.util.Base64

Closes gh-11318
j3graham 3 éve
szülő
commit
f3c96fa9cd

+ 0 - 6
build.gradle

@@ -105,12 +105,6 @@ updateDependenciesSettings {
 		alphaBetaVersions()
 		snapshotVersions()
 		addRule { components ->
-			components.withModule("commons-codec:commons-codec") { selection ->
-				ModuleComponentIdentifier candidate = selection.getCandidate();
-				if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
-					selection.reject("commons-codec updates break saml tests");
-				}
-			}
 			components.withModule("org.python:jython") { selection ->
 				ModuleComponentIdentifier candidate = selection.getCandidate();
 				if (!candidate.getVersion().equals(selection.getCurrentVersion())) {

+ 0 - 1
dependencies/spring-security-dependencies.gradle

@@ -23,7 +23,6 @@ dependencies {
 		api "com.squareup.okhttp3:mockwebserver:3.14.9"
 		api "com.squareup.okhttp3:okhttp:3.14.9"
 		api "com.unboundid:unboundid-ldapsdk:4.0.14"
-		api "commons-codec:commons-codec:1.15"
 		api "commons-collections:commons-collections:3.2.2"
 		api "io.mockk:mockk:1.12.4"
 		api "jakarta.annotation:jakarta.annotation-api:2.1.0"

+ 0 - 1
messaging/spring-security-messaging.gradle

@@ -15,7 +15,6 @@ dependencies {
 	optional 'jakarta.servlet:jakarta.servlet-api'
 
 	testImplementation project(path: ':spring-security-core', configuration: 'tests')
-	testImplementation 'commons-codec:commons-codec'
 	testImplementation "org.assertj:assertj-core"
 	testImplementation "org.junit.jupiter:junit-jupiter-api"
 	testImplementation "org.junit.jupiter:junit-jupiter-params"

+ 62 - 3
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java

@@ -18,13 +18,13 @@ package org.springframework.security.saml2.provider.service.web;
 
 import java.io.ByteArrayOutputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
 import java.util.function.Function;
 import java.util.zip.Inflater;
 import java.util.zip.InflaterOutputStream;
 
 import jakarta.servlet.http.HttpServletRequest;
-import org.apache.commons.codec.CodecPolicy;
-import org.apache.commons.codec.binary.Base64;
 
 import org.springframework.http.HttpMethod;
 import org.springframework.security.saml2.core.Saml2Error;
@@ -47,7 +47,11 @@ import org.springframework.util.Assert;
  */
 public final class Saml2AuthenticationTokenConverter implements AuthenticationConverter {
 
-	private static Base64 BASE64 = new Base64(0, new byte[] { '\n' }, false, CodecPolicy.STRICT);
+	// MimeDecoder allows extra line-breaks as well as other non-alphabet values.
+	// This matches the behaviour of the commons-codec decoder.
+	private static final Base64.Decoder BASE64 = Base64.getMimeDecoder();
+
+	private static final Base64Checker BASE_64_CHECKER = new Base64Checker();
 
 	private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
 
@@ -110,6 +114,7 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo
 
 	private byte[] samlDecode(String base64EncodedPayload) {
 		try {
+			BASE_64_CHECKER.checkAcceptable(base64EncodedPayload);
 			return BASE64.decode(base64EncodedPayload);
 		}
 		catch (Exception ex) {
@@ -132,4 +137,58 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo
 		}
 	}
 
+	static class Base64Checker {
+
+		private static final int[] values = genValueMapping();
+
+		Base64Checker() {
+
+		}
+
+		private static int[] genValueMapping() {
+			byte[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+					.getBytes(StandardCharsets.ISO_8859_1);
+
+			int[] values = new int[256];
+			Arrays.fill(values, -1);
+			for (int i = 0; i < alphabet.length; i++) {
+				values[alphabet[i] & 0xff] = i;
+			}
+			return values;
+		}
+
+		boolean isAcceptable(String s) {
+			int goodChars = 0;
+			int lastGoodCharVal = -1;
+
+			// count number of characters from Base64 alphabet
+			for (int i = 0; i < s.length(); i++) {
+				int val = values[0xff & s.charAt(i)];
+				if (val != -1) {
+					lastGoodCharVal = val;
+					goodChars++;
+				}
+			}
+
+			// in cases of an incomplete final chunk, ensure the unused bits are zero
+			switch (goodChars % 4) {
+			case 0:
+				return true;
+			case 2:
+				return (lastGoodCharVal & 0b1111) == 0;
+			case 3:
+				return (lastGoodCharVal & 0b11) == 0;
+			default:
+				return false;
+			}
+		}
+
+		void checkAcceptable(String ins) {
+			if (!isAcceptable(ins)) {
+				throw new IllegalArgumentException("Unaccepted Encoding");
+			}
+		}
+
+	}
+
 }

+ 0 - 1
web/spring-security-web.gradle

@@ -20,7 +20,6 @@ dependencies {
 	provided 'jakarta.servlet:jakarta.servlet-api'
 
 	testImplementation project(path: ':spring-security-core', configuration: 'tests')
-	testImplementation 'commons-codec:commons-codec'
 	testImplementation 'io.projectreactor:reactor-test'
 	testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
 	testImplementation 'org.hamcrest:hamcrest'

+ 55 - 0
web/src/test/java/org/springframework/security/test/web/CodecTestUtils.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.test.web;
+
+import java.util.Base64;
+
+import org.springframework.util.DigestUtils;
+
+public final class CodecTestUtils {
+
+	private CodecTestUtils() {
+	}
+
+	public static String encodeBase64(String unencoded) {
+		return Base64.getEncoder().encodeToString(unencoded.getBytes());
+	}
+
+	public static String encodeBase64(byte[] unencoded) {
+		return Base64.getEncoder().encodeToString(unencoded);
+	}
+
+	public static String decodeBase64(String encoded) {
+		return new String(Base64.getDecoder().decode(encoded));
+	}
+
+	public static boolean isBase64(byte[] arrayOctet) {
+		try {
+			Base64.getMimeDecoder().decode(arrayOctet);
+			return true;
+
+		}
+		catch (Exception ex) {
+			return false;
+		}
+	}
+
+	public static String md5Hex(String data) {
+		return DigestUtils.md5DigestAsHex(data.getBytes());
+	}
+
+}

+ 9 - 10
web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java

@@ -19,8 +19,6 @@ package org.springframework.security.web.authentication.rememberme;
 import java.util.Date;
 
 import jakarta.servlet.http.Cookie;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -33,6 +31,7 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.test.web.CodecTestUtils;
 import org.springframework.util.StringUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -76,7 +75,7 @@ public class TokenBasedRememberMeServicesTests {
 	}
 
 	private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
-		String cookieAsPlainText = new String(Base64.decodeBase64(validToken.getBytes()));
+		String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
 		String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");
 		if (cookieTokens.length == 3) {
 			try {
@@ -92,9 +91,9 @@ public class TokenBasedRememberMeServicesTests {
 		// format is:
 		// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" +
 		// password + ":" + key)
-		String signatureValue = DigestUtils.md5Hex(username + ":" + expiryTime + ":" + password + ":" + key);
+		String signatureValue = CodecTestUtils.md5Hex(username + ":" + expiryTime + ":" + password + ":" + key);
 		String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
-		return new String(Base64.encodeBase64(tokenValue.getBytes()));
+		return CodecTestUtils.encodeBase64(tokenValue);
 	}
 
 	@Test
@@ -134,7 +133,7 @@ public class TokenBasedRememberMeServicesTests {
 	@Test
 	public void autoLoginReturnsNullAndClearsCookieIfMissingThreeTokensInCookieValue() {
 		Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
-				new String(Base64.encodeBase64("x".getBytes())));
+				CodecTestUtils.encodeBase64("x"));
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.setCookies(cookie);
 		MockHttpServletResponse response = new MockHttpServletResponse();
@@ -175,7 +174,7 @@ public class TokenBasedRememberMeServicesTests {
 	@Test
 	public void autoLoginClearsCookieIfTokenDoesNotContainANumberInCookieValue() {
 		Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
-				new String(Base64.encodeBase64("username:NOT_A_NUMBER:signature".getBytes())));
+				CodecTestUtils.encodeBase64("username:NOT_A_NUMBER:signature"));
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.setCookies(cookie);
 		MockHttpServletResponse response = new MockHttpServletResponse();
@@ -275,7 +274,7 @@ public class TokenBasedRememberMeServicesTests {
 		assertThat(Long.parseLong(expiryTime) > expectedExpiryTime - 10000).isTrue();
 		assertThat(cookie).isNotNull();
 		assertThat(cookie.getMaxAge()).isEqualTo(this.services.getTokenValiditySeconds());
-		assertThat(Base64.isArrayByteBase64(cookie.getValue().getBytes())).isTrue();
+		assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
 		assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
 	}
 
@@ -289,7 +288,7 @@ public class TokenBasedRememberMeServicesTests {
 		Cookie cookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
 		assertThat(cookie).isNotNull();
 		assertThat(cookie.getMaxAge()).isEqualTo(this.services.getTokenValiditySeconds());
-		assertThat(Base64.isArrayByteBase64(cookie.getValue().getBytes())).isTrue();
+		assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
 		assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
 	}
 
@@ -315,7 +314,7 @@ public class TokenBasedRememberMeServicesTests {
 		assertThat(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())
 				- System.currentTimeMillis() > AbstractRememberMeServices.TWO_WEEKS_S - 50).isTrue();
 		assertThat(cookie.getMaxAge()).isEqualTo(-1);
-		assertThat(Base64.isArrayByteBase64(cookie.getValue().getBytes())).isTrue();
+		assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
 	}
 
 }

+ 5 - 5
web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java

@@ -17,7 +17,6 @@
 package org.springframework.security.web.authentication.www;
 
 import jakarta.servlet.http.HttpServletRequest;
-import org.apache.commons.codec.binary.Base64;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -28,6 +27,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.security.authentication.AuthenticationDetailsSource;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.test.web.CodecTestUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -56,7 +56,7 @@ public class BasicAuthenticationConverterTests {
 	public void testNormalOperation() {
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		UsernamePasswordAuthenticationToken authentication = this.converter.convert(request);
 		verify(this.authenticationDetailsSource).buildDetails(any());
 		assertThat(authentication).isNotNull();
@@ -67,7 +67,7 @@ public class BasicAuthenticationConverterTests {
 	public void requestWhenAuthorizationSchemeInMixedCaseThenAuthenticates() {
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "BaSiC " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "BaSiC " + CodecTestUtils.encodeBase64(token));
 		UsernamePasswordAuthenticationToken authentication = this.converter.convert(request);
 		verify(this.authenticationDetailsSource).buildDetails(any());
 		assertThat(authentication).isNotNull();
@@ -87,7 +87,7 @@ public class BasicAuthenticationConverterTests {
 	public void testWhenInvalidBasicAuthorizationTokenThenError() {
 		String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.converter.convert(request));
 	}
 
@@ -102,7 +102,7 @@ public class BasicAuthenticationConverterTests {
 	public void convertWhenEmptyPassword() {
 		String token = "rod:";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		UsernamePasswordAuthenticationToken authentication = this.converter.convert(request);
 		verify(this.authenticationDetailsSource).buildDetails(any());
 		assertThat(authentication).isNotNull();

+ 14 - 14
web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java

@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletRequest;
 import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
-import org.apache.commons.codec.binary.Base64;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -38,6 +37,7 @@ import org.springframework.security.core.Authentication;
 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.test.web.CodecTestUtils;
 import org.springframework.security.web.authentication.WebAuthenticationDetails;
 import org.springframework.security.web.context.SecurityContextRepository;
 import org.springframework.web.util.WebUtils;
@@ -104,7 +104,7 @@ public class BasicAuthenticationFilterTests {
 	public void testInvalidBasicAuthorizationTokenIsIgnored() throws Exception {
 		String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		request.setSession(new MockHttpSession());
 		final MockHttpServletResponse response = new MockHttpServletResponse();
@@ -134,7 +134,7 @@ public class BasicAuthenticationFilterTests {
 	public void testNormalOperation() throws Exception {
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		// Test
 		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
@@ -150,7 +150,7 @@ public class BasicAuthenticationFilterTests {
 	public void doFilterWhenSchemeLowercaseThenCaseInsensitveMatchWorks() throws Exception {
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		// Test
 		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
@@ -165,7 +165,7 @@ public class BasicAuthenticationFilterTests {
 	public void doFilterWhenSchemeMixedCaseThenCaseInsensitiveMatchWorks() throws Exception {
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "BaSiC " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "BaSiC " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
 		FilterChain chain = mock(FilterChain.class);
@@ -200,7 +200,7 @@ public class BasicAuthenticationFilterTests {
 	public void testSuccessLoginThenFailureLoginResultsInSessionLosingToken() throws Exception {
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		final MockHttpServletResponse response1 = new MockHttpServletResponse();
 		FilterChain chain = mock(FilterChain.class);
@@ -212,7 +212,7 @@ public class BasicAuthenticationFilterTests {
 		// NOW PERFORM FAILED AUTHENTICATION
 		token = "otherUser:WRONG_PASSWORD";
 		request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		final MockHttpServletResponse response2 = new MockHttpServletResponse();
 		chain = mock(FilterChain.class);
 		this.filter.doFilter(request, response2, chain);
@@ -228,7 +228,7 @@ public class BasicAuthenticationFilterTests {
 	public void testWrongPasswordContinuesFilterChainIfIgnoreFailureIsTrue() throws Exception {
 		String token = "rod:WRONG_PASSWORD";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		request.setSession(new MockHttpSession());
 		this.filter = new BasicAuthenticationFilter(this.manager);
@@ -244,7 +244,7 @@ public class BasicAuthenticationFilterTests {
 	public void testWrongPasswordReturnsForbiddenIfIgnoreFailureIsFalse() throws Exception {
 		String token = "rod:WRONG_PASSWORD";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		request.setSession(new MockHttpSession());
 		assertThat(this.filter.isIgnoreFailure()).isFalse();
@@ -262,7 +262,7 @@ public class BasicAuthenticationFilterTests {
 	public void skippedOnErrorDispatch() throws Exception {
 		String token = "bad:credentials";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
 		MockHttpServletResponse response = new MockHttpServletResponse();
@@ -286,7 +286,7 @@ public class BasicAuthenticationFilterTests {
 		String token = "rod:äöü";
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.addHeader("Authorization",
-				"Basic " + new String(Base64.encodeBase64(token.getBytes(StandardCharsets.UTF_8))));
+				"Basic " + CodecTestUtils.encodeBase64(token.getBytes(StandardCharsets.UTF_8)));
 		request.setServletPath("/some_file.html");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		// Test
@@ -315,7 +315,7 @@ public class BasicAuthenticationFilterTests {
 		String token = "rod:äöü";
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.addHeader("Authorization",
-				"Basic " + new String(Base64.encodeBase64(token.getBytes(StandardCharsets.ISO_8859_1))));
+				"Basic " + CodecTestUtils.encodeBase64(token.getBytes(StandardCharsets.ISO_8859_1)));
 		request.setServletPath("/some_file.html");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		// Test
@@ -344,7 +344,7 @@ public class BasicAuthenticationFilterTests {
 		String token = "rod:äöü";
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.addHeader("Authorization",
-				"Basic " + new String(Base64.encodeBase64(token.getBytes(StandardCharsets.UTF_8))));
+				"Basic " + CodecTestUtils.encodeBase64(token.getBytes(StandardCharsets.UTF_8)));
 		request.setServletPath("/some_file.html");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		// Test
@@ -377,7 +377,7 @@ public class BasicAuthenticationFilterTests {
 		this.filter.setSecurityContextRepository(securityContextRepository);
 		String token = "rod:koala";
 		MockHttpServletRequest request = new MockHttpServletRequest();
-		request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
+		request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
 		request.setServletPath("/some_file.html");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		// Test

+ 4 - 5
web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPointTests.java

@@ -18,13 +18,12 @@ package org.springframework.security.web.authentication.www;
 
 import java.util.Map;
 
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.test.web.CodecTestUtils;
 import org.springframework.util.StringUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -41,11 +40,11 @@ public class DigestAuthenticationEntryPointTests {
 		// Check the nonce seems to be generated correctly
 		// format of nonce is:
 		// base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
-		assertThat(Base64.isArrayByteBase64(nonce.getBytes())).isTrue();
-		String decodedNonce = new String(Base64.decodeBase64(nonce.getBytes()));
+		assertThat(CodecTestUtils.isBase64(nonce.getBytes())).isTrue();
+		String decodedNonce = CodecTestUtils.decodeBase64(nonce);
 		String[] nonceTokens = StringUtils.delimitedListToStringArray(decodedNonce, ":");
 		assertThat(nonceTokens).hasSize(2);
-		String expectedNonceSignature = DigestUtils.md5Hex(nonceTokens[0] + ":" + "key");
+		String expectedNonceSignature = CodecTestUtils.md5Hex(nonceTokens[0] + ":" + "key");
 		assertThat(nonceTokens[1]).isEqualTo(expectedNonceSignature);
 	}
 

+ 7 - 8
web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java

@@ -23,8 +23,6 @@ import jakarta.servlet.Filter;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.ServletRequest;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.digest.DigestUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -40,6 +38,7 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.cache.NullUserCache;
+import org.springframework.security.test.web.CodecTestUtils;
 import org.springframework.security.web.context.SecurityContextRepository;
 import org.springframework.util.StringUtils;
 
@@ -105,9 +104,9 @@ public class DigestAuthenticationFilterTests {
 
 	private static String generateNonce(int validitySeconds, String key) {
 		long expiryTime = System.currentTimeMillis() + (validitySeconds * 1000);
-		String signatureValue = DigestUtils.md5Hex(expiryTime + ":" + key);
+		String signatureValue = CodecTestUtils.md5Hex(expiryTime + ":" + key);
 		String nonceValue = expiryTime + ":" + signatureValue;
-		return new String(Base64.encodeBase64(nonceValue.getBytes()));
+		return CodecTestUtils.encodeBase64(nonceValue);
 	}
 
 	@AfterEach
@@ -182,7 +181,7 @@ public class DigestAuthenticationFilterTests {
 	@Test
 	public void testInvalidDigestAuthorizationTokenGeneratesError() throws Exception {
 		String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
-		this.request.addHeader("Authorization", "Digest " + new String(Base64.encodeBase64(token.getBytes())));
+		this.request.addHeader("Authorization", "Digest " + CodecTestUtils.encodeBase64(token));
 		MockHttpServletResponse response = executeFilterInContainerSimulator(this.filter, this.request, false);
 		assertThat(response.getStatus()).isEqualTo(401);
 		assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
@@ -210,7 +209,7 @@ public class DigestAuthenticationFilterTests {
 
 	@Test
 	public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden() throws Exception {
-		String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword".getBytes()));
+		String nonce = CodecTestUtils.encodeBase64("123456:incorrectStringPassword");
 		String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET", REQUEST_URI,
 				QOP, nonce, NC, CNONCE);
 		this.request.addHeader("Authorization",
@@ -222,7 +221,7 @@ public class DigestAuthenticationFilterTests {
 
 	@Test
 	public void testNonceWithNonNumericFirstElementReturnsForbidden() throws Exception {
-		String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement".getBytes()));
+		String nonce = CodecTestUtils.encodeBase64("hello:ignoredSecondElement");
 		String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET", REQUEST_URI,
 				QOP, nonce, NC, CNONCE);
 		this.request.addHeader("Authorization",
@@ -234,7 +233,7 @@ public class DigestAuthenticationFilterTests {
 
 	@Test
 	public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden() throws Exception {
-		String nonce = new String(Base64.encodeBase64("a base 64 string without a colon".getBytes()));
+		String nonce = CodecTestUtils.encodeBase64("a base 64 string without a colon");
 		String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET", REQUEST_URI,
 				QOP, nonce, NC, CNONCE);
 		this.request.addHeader("Authorization",