mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-03-11 00:54:32 +00:00
Specify charset in WWW-Authenticate for Basic Auth
In this commit, we add support for the charset from RFC-7617, which definitely solves the problem when the client does not know what charset we are parsing with. Closes: gh-18755 Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
This commit is contained in:
parent
c7235ec0a3
commit
d1ce69ca99
@ -197,7 +197,7 @@ public class NamespaceHttpTests {
|
||||
// @formatter:off
|
||||
this.mockMvc.perform(get("/"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"RealmConfig\""));
|
||||
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"RealmConfig\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ public class HttpBasicConfigurerTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\""));
|
||||
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ public class HttpBasicConfigurerTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\""));
|
||||
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ public class NamespaceHttpBasicTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(requestWithInvalidPassword)
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\""));
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
MockHttpServletRequestBuilder requestWithValidPassword = get("/").with(httpBasic("user", "password"));
|
||||
this.mvc.perform(requestWithValidPassword).andExpect(status().isNotFound());
|
||||
@ -85,7 +85,7 @@ public class NamespaceHttpBasicTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(requestWithInvalidPassword)
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\""));
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
MockHttpServletRequestBuilder requestWithValidPassword = get("/").with(httpBasic("user", "password"));
|
||||
this.mvc.perform(requestWithValidPassword).andExpect(status().isNotFound());
|
||||
@ -101,7 +101,7 @@ public class NamespaceHttpBasicTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(requestWithInvalidPassword)
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\""));
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ public class NamespaceHttpBasicTests {
|
||||
// @formatter:off
|
||||
this.mvc.perform(requestWithInvalidPassword)
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\""));
|
||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\", charset=\"UTF-8\""));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ public class NamespaceHttpBasicTests {
|
||||
// @formatter:on
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
assertThat(this.response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"Realm\"");
|
||||
assertThat(this.response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"Realm\", charset=\"UTF-8\"");
|
||||
}
|
||||
|
||||
private void loadContext(String context) {
|
||||
|
||||
@ -74,7 +74,7 @@ class HttpBasicDslTests {
|
||||
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
header { string("WWW-Authenticate", "Basic realm=\"Realm\"") }
|
||||
header { string("WWW-Authenticate", "Basic realm=\"Realm\", charset=\"UTF-8\"") }
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ class HttpBasicDslTests {
|
||||
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
header { string("WWW-Authenticate", "Basic realm=\"Custom Realm\"") }
|
||||
header { string("WWW-Authenticate", "Basic realm=\"Custom Realm\", charset=\"UTF-8\"") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
package org.springframework.security.web.authentication.www;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@ -40,11 +42,14 @@ import org.springframework.util.Assert;
|
||||
* authorized, causing it to prompt the user to login again.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Andrey Litvitski
|
||||
*/
|
||||
public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
|
||||
|
||||
private @Nullable String realmName;
|
||||
|
||||
private @Nullable Charset charset = StandardCharsets.UTF_8;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasText(this.realmName, "realmName must be specified");
|
||||
@ -53,7 +58,11 @@ public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint,
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
response.setHeader("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
|
||||
String header = "Basic realm=\"" + this.realmName + "\"";
|
||||
if (this.charset != null) {
|
||||
header += ", charset=\"" + this.charset.name() + "\"";
|
||||
}
|
||||
response.setHeader("WWW-Authenticate", header);
|
||||
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
|
||||
}
|
||||
|
||||
@ -65,4 +74,17 @@ public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint,
|
||||
this.realmName = realmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charset to include in the {@code WWW-Authenticate} response header. By
|
||||
* default, it is set to {@link StandardCharsets#UTF_8}. Set to {@code null} to omit
|
||||
* the charset attribute from the header. As per RFC 7617, only UTF-8 is permitted.
|
||||
* @param charset the charset to use ({@link StandardCharsets#UTF_8} is the only
|
||||
* accepted value), or {@code null} to remove the charset attribute
|
||||
*/
|
||||
public void setCharset(@Nullable Charset charset) {
|
||||
Assert.isTrue(charset == null || StandardCharsets.UTF_8.equals(charset),
|
||||
"RFC 7617 only permits UTF-8 as the charset for Basic authentication");
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
* Tests {@link BasicAuthenticationEntryPoint}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Andrey Litvitski
|
||||
*/
|
||||
public class BasicAuthenticationEntryPointTests {
|
||||
|
||||
@ -62,7 +64,7 @@ public class BasicAuthenticationEntryPointTests {
|
||||
ep.commence(request, response, new DisabledException("These are the jokes kid"));
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
assertThat(response.getErrorMessage()).isEqualTo(HttpStatus.UNAUTHORIZED.getReasonPhrase());
|
||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"hello\"");
|
||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"hello\", charset=\"UTF-8\"");
|
||||
}
|
||||
|
||||
// gh-13737
|
||||
@ -77,7 +79,30 @@ public class BasicAuthenticationEntryPointTests {
|
||||
ep.commence(request, response, new DisabledException("Disabled"));
|
||||
List<String> headers = response.getHeaders("WWW-Authenticate");
|
||||
assertThat(headers).hasSize(1);
|
||||
assertThat(headers.get(0)).isEqualTo("Basic realm=\"hello\"");
|
||||
assertThat(headers.get(0)).isEqualTo("Basic realm=\"hello\", charset=\"UTF-8\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void commenceWhenDefaultThenIncludesUtf8Charset() throws Exception {
|
||||
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
|
||||
entryPoint.setRealmName("TestRealm");
|
||||
entryPoint.afterPropertiesSet();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
entryPoint.commence(request, response, new BadCredentialsException("test"));
|
||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"TestRealm\", charset=\"UTF-8\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void commenceWhenCharsetIsNullThenOmitsCharset() throws Exception {
|
||||
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
|
||||
entryPoint.setRealmName("TestRealm");
|
||||
entryPoint.setCharset(null);
|
||||
entryPoint.afterPropertiesSet();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
entryPoint.commence(request, response, new BadCredentialsException("test"));
|
||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"TestRealm\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user