From f3c96fa9cd542763180121be64e72ca88302d346 Mon Sep 17 00:00:00 2001 From: j3graham Date: Wed, 1 Jun 2022 14:32:55 -0400 Subject: [PATCH] Remove dependency on commons-codec by using java.util.Base64 Closes gh-11318 --- build.gradle | 6 -- .../spring-security-dependencies.gradle | 1 - messaging/spring-security-messaging.gradle | 1 - .../Saml2AuthenticationTokenConverter.java | 65 ++++++++++++++++++- web/spring-security-web.gradle | 1 - .../security/test/web/CodecTestUtils.java | 55 ++++++++++++++++ .../TokenBasedRememberMeServicesTests.java | 19 +++--- .../BasicAuthenticationConverterTests.java | 10 +-- .../www/BasicAuthenticationFilterTests.java | 28 ++++---- .../DigestAuthenticationEntryPointTests.java | 9 ++- .../www/DigestAuthenticationFilterTests.java | 15 ++--- 11 files changed, 156 insertions(+), 54 deletions(-) create mode 100644 web/src/test/java/org/springframework/security/test/web/CodecTestUtils.java diff --git a/build.gradle b/build.gradle index 47f20b22bd..70bf7349bc 100644 --- a/build.gradle +++ b/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())) { diff --git a/dependencies/spring-security-dependencies.gradle b/dependencies/spring-security-dependencies.gradle index cd01a02cfd..c78bac1494 100644 --- a/dependencies/spring-security-dependencies.gradle +++ b/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" diff --git a/messaging/spring-security-messaging.gradle b/messaging/spring-security-messaging.gradle index 3ae0b4f23a..64435e64dd 100644 --- a/messaging/spring-security-messaging.gradle +++ b/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" diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java index 56cefd4e00..cb29a370b0 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java +++ b/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"); + } + } + + } + } diff --git a/web/spring-security-web.gradle b/web/spring-security-web.gradle index 81a4e8116f..ca63924bd0 100644 --- a/web/spring-security-web.gradle +++ b/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' diff --git a/web/src/test/java/org/springframework/security/test/web/CodecTestUtils.java b/web/src/test/java/org/springframework/security/test/web/CodecTestUtils.java new file mode 100644 index 0000000000..e05ca3b6d8 --- /dev/null +++ b/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()); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java b/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java index e4a74248f1..d90f53ee10 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServicesTests.java +++ b/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(); } } diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java index c309de5a65..1b8473674d 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java +++ b/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(); diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java index 9f4826aca1..c6a52952bd 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java +++ b/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 diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPointTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPointTests.java index 94cd7b8887..1e9f557c0d 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPointTests.java +++ b/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); } diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java index 321f6b8167..f46582c617 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java +++ b/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",