Skip to content

Commit 7e9d707

Browse files
Allow customize the AuthenticationConverter in BasicAuthenticationFilter
Closes gh-13988
1 parent c08baea commit 7e9d707

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@
2828
import org.springframework.security.authentication.AnonymousAuthenticationToken;
2929
import org.springframework.security.authentication.AuthenticationDetailsSource;
3030
import org.springframework.security.authentication.AuthenticationManager;
31-
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
3231
import org.springframework.security.core.Authentication;
3332
import org.springframework.security.core.AuthenticationException;
3433
import org.springframework.security.core.context.SecurityContext;
3534
import org.springframework.security.core.context.SecurityContextHolder;
3635
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3736
import org.springframework.security.web.AuthenticationEntryPoint;
37+
import org.springframework.security.web.authentication.AuthenticationConverter;
3838
import org.springframework.security.web.authentication.NullRememberMeServices;
3939
import org.springframework.security.web.authentication.RememberMeServices;
40+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
4041
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
4142
import org.springframework.security.web.context.SecurityContextRepository;
4243
import org.springframework.util.Assert;
@@ -105,7 +106,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
105106

106107
private String credentialsCharset = "UTF-8";
107108

108-
private BasicAuthenticationConverter authenticationConverter = new BasicAuthenticationConverter();
109+
private AuthenticationConverter authenticationConverter = new BasicAuthenticationConverter();
109110

110111
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();
111112

@@ -149,6 +150,18 @@ public void setSecurityContextRepository(SecurityContextRepository securityConte
149150
this.securityContextRepository = securityContextRepository;
150151
}
151152

153+
/**
154+
* Sets the
155+
* {@link org.springframework.security.web.authentication.AuthenticationConverter} to
156+
* use. Defaults to {@link BasicAuthenticationConverter}
157+
* @param authenticationConverter the converter to use
158+
* @since 6.2
159+
*/
160+
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
161+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
162+
this.authenticationConverter = authenticationConverter;
163+
}
164+
152165
@Override
153166
public void afterPropertiesSet() {
154167
Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
@@ -161,7 +174,7 @@ public void afterPropertiesSet() {
161174
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
162175
throws IOException, ServletException {
163176
try {
164-
UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);
177+
Authentication authRequest = this.authenticationConverter.convert(request);
165178
if (authRequest == null) {
166179
this.logger.trace("Did not process authentication request since failed to find "
167180
+ "username and password in Basic Authorization header");
@@ -250,20 +263,40 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur
250263
this.securityContextHolderStrategy = securityContextHolderStrategy;
251264
}
252265

266+
/**
267+
* Sets the {@link AuthenticationDetailsSource} to use. By default, it is set to use
268+
* the {@link WebAuthenticationDetailsSource}. Note that this configuration applies
269+
* exclusively when the {@link #authenticationConverter} is set to
270+
* {@link BasicAuthenticationConverter}. If you are utilizing a different
271+
* implementation, you will need to manually specify the authentication details on it.
272+
* @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use.
273+
*/
253274
public void setAuthenticationDetailsSource(
254275
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
255-
this.authenticationConverter.setAuthenticationDetailsSource(authenticationDetailsSource);
276+
if (this.authenticationConverter instanceof BasicAuthenticationConverter basicAuthenticationConverter) {
277+
basicAuthenticationConverter.setAuthenticationDetailsSource(authenticationDetailsSource);
278+
}
256279
}
257280

258281
public void setRememberMeServices(RememberMeServices rememberMeServices) {
259282
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
260283
this.rememberMeServices = rememberMeServices;
261284
}
262285

286+
/**
287+
* Sets the charset to use when decoding credentials to {@link String}s. By default,
288+
* it is set to {@code UTF-8}. Note that this configuration applies exclusively when
289+
* the {@link #authenticationConverter} is set to
290+
* {@link BasicAuthenticationConverter}. If you are utilizing a different
291+
* implementation, you will need to manually specify the charset on it.
292+
* @param credentialsCharset the charset to use.
293+
*/
263294
public void setCredentialsCharset(String credentialsCharset) {
264295
Assert.hasText(credentialsCharset, "credentialsCharset cannot be null or empty");
265296
this.credentialsCharset = credentialsCharset;
266-
this.authenticationConverter.setCredentialsCharset(Charset.forName(credentialsCharset));
297+
if (this.authenticationConverter instanceof BasicAuthenticationConverter basicAuthenticationConverter) {
298+
basicAuthenticationConverter.setCredentialsCharset(Charset.forName(credentialsCharset));
299+
}
267300
}
268301

269302
protected String getCredentialsCharset(HttpServletRequest httpRequest) {

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import jakarta.servlet.FilterChain;
2222
import jakarta.servlet.ServletRequest;
2323
import jakarta.servlet.ServletResponse;
24+
import jakarta.servlet.http.HttpServletRequest;
2425
import jakarta.servlet.http.HttpServletResponse;
2526
import org.junit.jupiter.api.AfterEach;
2627
import org.junit.jupiter.api.BeforeEach;
@@ -41,9 +42,12 @@
4142
import org.springframework.security.core.context.SecurityContextHolder;
4243
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4344
import org.springframework.security.test.web.CodecTestUtils;
45+
import org.springframework.security.web.authentication.AuthenticationConverter;
4446
import org.springframework.security.web.authentication.WebAuthenticationDetails;
4547
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
4648
import org.springframework.security.web.context.SecurityContextRepository;
49+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
50+
import org.springframework.security.web.util.matcher.RequestMatcher;
4751
import org.springframework.web.util.WebUtils;
4852

4953
import static org.assertj.core.api.Assertions.assertThat;
@@ -488,4 +492,57 @@ public void doFilterWhenUsernameChangesAndNotUsernamePasswordAuthenticationToken
488492
assertThat(authenticationRequest.getName()).isEqualTo("rod");
489493
}
490494

495+
@Test
496+
public void doFilterWhenCustomAuthenticationConverterThatIgnoresRequestThenIgnores() throws Exception {
497+
this.filter.setAuthenticationConverter(new TestAuthenticationConverter());
498+
String token = "rod:koala";
499+
MockHttpServletRequest request = new MockHttpServletRequest();
500+
request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
501+
request.setServletPath("/ignored");
502+
FilterChain filterChain = mock(FilterChain.class);
503+
MockHttpServletResponse response = new MockHttpServletResponse();
504+
this.filter.doFilter(request, response, filterChain);
505+
assertThat(response.getStatus()).isEqualTo(200);
506+
507+
verify(this.manager, never()).authenticate(any(Authentication.class));
508+
verify(filterChain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
509+
verifyNoMoreInteractions(this.manager, filterChain);
510+
}
511+
512+
@Test
513+
public void doFilterWhenCustomAuthenticationConverterRequestThenAuthenticate() throws Exception {
514+
this.filter.setAuthenticationConverter(new TestAuthenticationConverter());
515+
String token = "rod:koala";
516+
MockHttpServletRequest request = new MockHttpServletRequest();
517+
request.addHeader("Authorization", "Basic " + CodecTestUtils.encodeBase64(token));
518+
request.setServletPath("/ok");
519+
FilterChain filterChain = mock(FilterChain.class);
520+
MockHttpServletResponse response = new MockHttpServletResponse();
521+
this.filter.doFilter(request, response, filterChain);
522+
assertThat(response.getStatus()).isEqualTo(200);
523+
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
524+
assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("rod");
525+
}
526+
527+
@Test
528+
public void setAuthenticationConverterWhenNullThenException() {
529+
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationConverter(null));
530+
}
531+
532+
static class TestAuthenticationConverter implements AuthenticationConverter {
533+
534+
private final RequestMatcher matcher = AntPathRequestMatcher.antMatcher("/ignored");
535+
536+
private final BasicAuthenticationConverter delegate = new BasicAuthenticationConverter();
537+
538+
@Override
539+
public Authentication convert(HttpServletRequest request) {
540+
if (this.matcher.matches(request)) {
541+
return null;
542+
}
543+
return this.delegate.convert(request);
544+
}
545+
546+
}
547+
491548
}

0 commit comments

Comments
 (0)