diff --git a/web/src/main/java/org/springframework/security/web/server/ServerHttpBasicAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/server/ServerHttpBasicAuthenticationConverter.java index 3f58b31ec7c..4dcf10d4734 100644 --- a/web/src/main/java/org/springframework/security/web/server/ServerHttpBasicAuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/server/ServerHttpBasicAuthenticationConverter.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.function.Function; @@ -25,6 +27,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; @@ -43,6 +46,8 @@ public class ServerHttpBasicAuthenticationConverter implements Function apply(ServerWebExchange exchange) { @@ -51,9 +56,8 @@ public Mono apply(ServerWebExchange exchange) { if (!StringUtils.startsWithIgnoreCase(authorization, "basic ")) { return Mono.empty(); } - String credentials = (authorization.length() <= BASIC.length()) ? "" - : authorization.substring(BASIC.length(), authorization.length()); - String decoded = new String(base64Decode(credentials)); + String credentials = (authorization.length() <= BASIC.length()) ? "" : authorization.substring(BASIC.length()); + String decoded = new String(base64Decode(credentials), this.credentialsCharset); String[] parts = decoded.split(":", 2); if (parts.length != 2) { return Mono.empty(); @@ -70,4 +74,13 @@ private byte[] base64Decode(String value) { } } + public Charset getCredentialsCharset() { + return this.credentialsCharset; + } + + public void setCredentialsCharset(Charset credentialsCharset) { + Assert.notNull(credentialsCharset, "credentialsCharset cannot be null"); + this.credentialsCharset = credentialsCharset; + } + } diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/ServerHttpBasicAuthenticationConverterTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/ServerHttpBasicAuthenticationConverterTests.java index 1f4a0d2a3f5..da76c0112d4 100644 --- a/web/src/test/java/org/springframework/security/web/server/authentication/ServerHttpBasicAuthenticationConverterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/authentication/ServerHttpBasicAuthenticationConverterTests.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server.authentication; +import java.nio.charset.StandardCharsets; + import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -62,7 +64,7 @@ public void applyWhenNotBase64ThenEmpty() { } @Test - public void applyWhenNoSemicolonThenEmpty() { + public void applyWhenNoColonThenEmpty() { Mono result = apply(this.request.header(HttpHeaders.AUTHORIZATION, "Basic dXNlcg==")); assertThat(result.block()).isNull(); } @@ -104,6 +106,38 @@ public void applyWhenWrongSchemeThenEmpty() { assertThat(result.block()).isNull(); } + @Test + public void applyWhenNonAsciiThenAuthentication() { + Mono result = apply( + this.request.header(HttpHeaders.AUTHORIZATION, "Basic w7xzZXI6cGFzc3fDtnJk")); + UsernamePasswordAuthenticationToken authentication = result.cast(UsernamePasswordAuthenticationToken.class) + .block(); + assertThat(authentication.getPrincipal()).isEqualTo("üser"); + assertThat(authentication.getCredentials()).isEqualTo("passwörd"); + } + + @Test + public void applyWhenIsoOnlyAsciiThenAuthentication() { + this.converter.setCredentialsCharset(StandardCharsets.ISO_8859_1); + Mono result = apply( + this.request.header(HttpHeaders.AUTHORIZATION, "Basic dXNlcjpwYXNzd29yZA==")); + UsernamePasswordAuthenticationToken authentication = result.cast(UsernamePasswordAuthenticationToken.class) + .block(); + assertThat(authentication.getPrincipal()).isEqualTo("user"); + assertThat(authentication.getCredentials()).isEqualTo("password"); + } + + @Test + public void applyWhenIsoNonAsciiThenAuthentication() { + this.converter.setCredentialsCharset(StandardCharsets.ISO_8859_1); + Mono result = apply( + this.request.header(HttpHeaders.AUTHORIZATION, "Basic /HNlcjpwYXNzd/ZyZA==")); + UsernamePasswordAuthenticationToken authentication = result.cast(UsernamePasswordAuthenticationToken.class) + .block(); + assertThat(authentication.getPrincipal()).isEqualTo("üser"); + assertThat(authentication.getCredentials()).isEqualTo("passwörd"); + } + private Mono apply(MockServerHttpRequest.BaseBuilder request) { return this.converter.convert(MockServerWebExchange.from(this.request.build())); }