Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public void configureWhenHttpBasicAndRequestUnauthorizedThenReturnWWWAuthenticat
// @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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void httpBasicWhenUsingDefaultsInLambdaThenResponseIncludesBasicChallenge
// @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
}

Expand All @@ -114,7 +114,7 @@ public void httpBasicWhenUsingDefaultsThenResponseIncludesBasicChallenge() throw
// @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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void basicAuthenticationWhenUsingDefaultsThenMatchesNamespace() throws Ex
// @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());
Expand All @@ -85,7 +85,7 @@ public void basicAuthenticationWhenUsingDefaultsInLambdaThenMatchesNamespace() t
// @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());
Expand All @@ -101,7 +101,7 @@ public void basicAuthenticationWhenUsingCustomRealmThenMatchesNamespace() throws
// @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
}

Expand All @@ -112,7 +112,7 @@ public void basicAuthenticationWhenUsingCustomRealmInLambdaThenMatchesNamespace(
// @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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void httpBasicUnauthorizedOnDefault() throws Exception {
// @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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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\"") }
}
}

Expand Down Expand Up @@ -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\"") }
}
}

Expand Down
3 changes: 3 additions & 0 deletions docs/modules/ROOT/pages/whats-new.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[[new]]
= What's New in Spring Security 7.1

== Web

* https://github.com/spring-projects/spring-security/pull/18634[gh-18634] - Added javadoc:org.springframework.security.web.util.matcher.InetAddressMatcher[]
* https://github.com/spring-projects/spring-security/issues/18755[gh-18755] - Include `charset` in `WWW-Authenticate` header
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,11 +42,14 @@
* 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");
Expand All @@ -53,7 +58,11 @@ public void afterPropertiesSet() {
@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());
}

Expand All @@ -65,4 +74,18 @@ public void setRealmName(String realmName) {
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
* @since 7.1
*/
public void setCharset(@Nullable Charset charset) {
Comment thread
jzheaux marked this conversation as resolved.
Assert.isTrue(charset == null || StandardCharsets.UTF_8.equals(charset),
"RFC 7617 only permits UTF-8 as the charset for Basic authentication");
this.charset = charset;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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;
Expand All @@ -34,6 +35,7 @@
* Tests {@link BasicAuthenticationEntryPoint}.
*
* @author Ben Alex
* @author Andrey Litvitski
*/
public class BasicAuthenticationEntryPointTests {

Expand Down Expand Up @@ -62,7 +64,7 @@ public void testNormalOperation() throws Exception {
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\"");
Comment thread
jzheaux marked this conversation as resolved.
}

// gh-13737
Expand All @@ -77,7 +79,30 @@ void commenceWhenResponseHasHeaderThenOverride() throws IOException {
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\"");
Comment thread
jzheaux marked this conversation as resolved.
}

@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