issue/6506: AuthenticationConverter implementation

This commit is contained in:
sbespalov 2019-06-20 10:30:40 +07:00 committed by Rob Winch
parent e584207a85
commit f1187bdfc2
9 changed files with 761 additions and 53 deletions

View File

@ -0,0 +1,40 @@
/*
* Copyright 2002-2019 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.web.authentication;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* A strategy used for converting from a {@link HttpServletRequest} to an
* {@link Authentication} of particular type. Used to authenticate with
* appropriate {@link AuthenticationManager}. If the result is null, then it
* signals that no authentication attempt should be made. It is also possible to
* throw {@link AuthenticationException} within the
* {@link #convert(HttpServletRequest)} if there was invalid Authentication
* scheme value.
*
* @author Sergey Bespalov
* @since 5.2.0
*/
public interface AuthenticationConverter {
Authentication convert(HttpServletRequest request);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-2019 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.web.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.Assert;
/**
* Adapts a {@link AuthenticationEntryPoint} into a {@link AuthenticationFailureHandler}
*
* @author sbespalov
* @since 5.2.0
*/
public class AuthenticationEntryPointFailureHandler implements AuthenticationFailureHandler {
private final AuthenticationEntryPoint authenticationEntryPoint;
public AuthenticationEntryPointFailureHandler(AuthenticationEntryPoint authenticationEntryPoint) {
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
this.authenticationEntryPoint.commence(request, response, exception);
}
}

View File

@ -0,0 +1,186 @@
/*
* Copyright 2002-2019 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.web.authentication;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* A {@link Filter} that performs authentication of a particular request. An
* outline of the logic:
*
* <ul>
* <li>A request comes in and if it does not match
* {@link #setRequestMatcher(RequestMatcher)}, then this filter does nothing and
* the {@link FilterChain} is continued. If it does match then...</li>
* <li>An attempt to convert the {@link HttpServletRequest} into an
* {@link Authentication} is made. If the result is empty, then the filter does
* nothing more and the {@link FilterChain} is continued. If it does create an
* {@link Authentication}...</li>
* <li>The {@link AuthenticationManager} specified in
* {@link #GenericAuthenticationFilter(AuthenticationManager)} is used to
* perform authentication.</li>
* <li>The {@link AuthenticationManagerResolver} specified in
* {@link #GenericAuthenticationFilter(AuthenticationManagerResolver)} is used
* to resolve the appropriate authentication manager from context to perform
* authentication.</li>
* <li>If authentication is successful, {@link AuthenticationSuccessHandler} is
* invoked and the authentication is set on {@link SecurityContextHolder}, else
* {@link AuthenticationFailureHandler} is invoked</li>
* </ul>
*
* @author Sergey Bespalov
* @since 5.2.0
*/
public class AuthenticationFilter extends OncePerRequestFilter {
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
private AuthenticationConverter authenticationConverter;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new AuthenticationEntryPointFailureHandler(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
public AuthenticationFilter(AuthenticationManager authenticationManager,
AuthenticationConverter authenticationConverter) {
this((AuthenticationManagerResolver<HttpServletRequest>) r -> authenticationManager, authenticationConverter);
}
public AuthenticationFilter(AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver,
AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationManagerResolver, "authenticationResolverManager cannot be null");
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationManagerResolver = authenticationManagerResolver;
this.authenticationConverter = authenticationConverter;
}
public RequestMatcher getRequestMatcher() {
return requestMatcher;
}
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requestMatcher = requestMatcher;
}
public AuthenticationConverter getAuthenticationConverter() {
return authenticationConverter;
}
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
public AuthenticationSuccessHandler getSuccessHandler() {
return successHandler;
}
public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
public AuthenticationFailureHandler getFailureHandler() {
return failureHandler;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
public AuthenticationManagerResolver<HttpServletRequest> getAuthenticationManagerResolver() {
return authenticationManagerResolver;
}
public void setAuthenticationManagerResolver(
AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver) {
Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null");
this.authenticationManagerResolver = authenticationManagerResolver;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!requestMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
filterChain.doFilter(request, response);
return;
}
successfulAuthentication(request, response, filterChain, authenticationResult);
} catch (AuthenticationException e) {
unsuccessfulAuthentication(request, response, e);
}
}
private void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, failed);
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authentication) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
successHandler.onAuthenticationSuccess(request, response, chain, authentication);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Authentication authentication = authenticationConverter.convert(request);
if (authentication == null) {
return null;
}
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(request);
Authentication authenticationResult = authenticationManager.authenticate(authentication);
if (authenticationResult == null) {
throw new ServletException("AuthenticationManager should not return null Authentication object.");
}
return authenticationResult;
}
}

View File

@ -17,6 +17,7 @@ package org.springframework.security.web.authentication;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -38,6 +39,23 @@ import org.springframework.security.core.Authentication;
*/
public interface AuthenticationSuccessHandler {
/**
* Called when a user has been successfully authenticated.
*
* @param request the request which caused the successful authentication
* @param response the response
* @param chain the {@link FilterChain} which can be used to proceed other filters in the chain
* @param authentication the <tt>Authentication</tt> object which was created during
* the authentication process.
* @since 5.2.0
*/
default void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException{
onAuthenticationSuccess(request, response, authentication);
chain.doFilter(request, response);
}
/**
* Called when a user has been successfully authenticated.
*

View File

@ -0,0 +1,121 @@
/*
* Copyright 2002-2019 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.web.authentication.www;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.Assert;
/**
* Converts from a HttpServletRequest to
* {@link UsernamePasswordAuthenticationToken} that can be authenticated. Null
* authentication possible if there was no Authorization header with Basic
* authentication scheme.
*
* @author Sergey Bespalov
* @since 5.2.0
*/
public class BasicAuthenticationConverter implements AuthenticationConverter {
public static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
private String credentialsCharset = StandardCharsets.UTF_8.name();
public BasicAuthenticationConverter() {
this(new WebAuthenticationDetailsSource());
}
public BasicAuthenticationConverter(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
}
public String getCredentialsCharset() {
return credentialsCharset;
}
public void setCredentialsCharset(String credentialsCharset) {
this.credentialsCharset = credentialsCharset;
}
public AuthenticationDetailsSource<HttpServletRequest, ?> getAuthenticationDetailsSource() {
return authenticationDetailsSource;
}
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
@Override
public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) {
String header = request.getHeader(AUTHORIZATION);
if (header == null) {
return null;
}
header = header.trim();
if (!header.startsWith(AUTHENTICATION_SCHEME_BASIC) && !header.startsWith(AUTHENTICATION_SCHEME_BASIC.toLowerCase())) {
return null;
}
byte[] base64Token = header.substring(6).getBytes();
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException e) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
String token;
try {
token = new String(decoded, getCredentialsCharset(request));
} catch (UnsupportedEncodingException e) {
throw new InternalAuthenticationServiceException(e.getMessage(), e);
}
String[] tokens = token.split(":");
if (tokens.length != 2) {
throw new BadCredentialsException("Invalid basic authentication token");
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(tokens[0],
tokens[1]);
authentication.setDetails(authenticationDetailsSource.buildDetails(request));
return authentication;
}
protected String getCredentialsCharset(HttpServletRequest request) {
return getCredentialsCharset();
}
}

View File

@ -17,7 +17,6 @@
package org.springframework.security.web.authentication.www;
import java.io.IOException;
import java.util.Base64;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@ -27,7 +26,6 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -35,9 +33,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
@ -95,12 +91,12 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
// ~ Instance fields
// ================================================================================================
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private boolean ignoreFailure = false;
private String credentialsCharset = "UTF-8";
private BasicAuthenticationConverter authenticationConverter = new BasicAuthenticationConverter();
/**
* Creates an instance which will authenticate against the supplied
@ -152,19 +148,14 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
String header = request.getHeader("Authorization");
if (!StringUtils.startsWithIgnoreCase(header, "basic ")) {
chain.doFilter(request, response);
return;
}
try {
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
if (authRequest == null) {
chain.doFilter(request, response);
return;
}
String username = tokens[0];
String username = authRequest.getName();
if (debug) {
this.logger
@ -173,10 +164,6 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
}
if (authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, tokens[1]);
authRequest.setDetails(
this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
@ -216,35 +203,6 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
chain.doFilter(request, response);
}
/**
* Decodes the header into a username and password.
*
* @throws BadCredentialsException if the Basic header is not present or is not valid
* Base64
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, getCredentialsCharset(request));
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
private boolean authenticationIsRequired(String username) {
// Only reauthenticate if username doesn't match SecurityContextHolder and user
// isn't authenticated
@ -308,9 +266,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource,
"AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
authenticationConverter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {

View File

@ -99,7 +99,7 @@ public class AbstractAuthenticationProcessingFilterTests {
}
@Test
public void testDefaultProcessesFilterUrlMatchesWithPathParameter() {
public void testDefaultProcessesFilterUrlMatchesWithPathParameter() throws Exception {
MockHttpServletRequest request = createMockAuthenticationRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockAuthenticationFilter filter = new MockAuthenticationFilter();

View File

@ -0,0 +1,249 @@
/*
* Copyright 2002-2019 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.web.authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* @author Sergey Bespalov
* @since 5.2.0
*/
@RunWith(MockitoJUnitRunner.class)
public class AuthenticationFilterTests {
@Mock
private AuthenticationSuccessHandler successHandler;
@Mock
private AuthenticationConverter authenticationConverter;
@Mock
private AuthenticationManager authenticationManager;
@Mock
private AuthenticationFailureHandler failureHandler;
@Mock
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
@Mock
private RequestMatcher requestMatcher;
@Before
public void setup() {
when(this.authenticationManagerResolver.resolve(any())).thenReturn(this.authenticationManager);
}
@After
public void clearContext() throws Exception {
SecurityContextHolder.clearContext();
}
@Test
public void filterWhenDefaultsAndNoAuthenticationThenContinues() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManager, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verifyZeroInteractions(this.authenticationManager);
verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void filterWhenAuthenticationManagerResolverDefaultsAndNoAuthenticationThenContinues() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verifyZeroInteractions(this.authenticationManagerResolver);
verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void filterWhenDefaultsAndAuthenticationSuccessThenContinues() throws Exception {
Authentication authentication = new TestingAuthenticationToken("test", "this", "ROLE");
when(this.authenticationConverter.convert(any())).thenReturn(authentication);
when(this.authenticationManager.authenticate(any())).thenReturn(authentication);
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManager, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(this.authenticationManager).authenticate(any(Authentication.class));
verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
}
@Test
public void filterWhenAuthenticationManagerResolverDefaultsAndAuthenticationSuccessThenContinues()
throws Exception {
Authentication authentication = new TestingAuthenticationToken("test", "this", "ROLE");
when(this.authenticationConverter.convert(any())).thenReturn(authentication);
when(this.authenticationManager.authenticate(any())).thenReturn(authentication);
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(this.authenticationManager).authenticate(any(Authentication.class));
verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
}
@Test
public void filterWhenDefaultsAndAuthenticationFailThenUnauthorized() throws Exception {
Authentication authentication = new TestingAuthenticationToken("test", "this", "ROLE");
when(this.authenticationConverter.convert(any())).thenReturn(authentication);
when(this.authenticationManager.authenticate(any())).thenThrow(new BadCredentialsException("failed"));
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManager, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void filterWhenAuthenticationManagerResolverDefaultsAndAuthenticationFailThenUnauthorized()
throws Exception {
Authentication authentication = new TestingAuthenticationToken("test", "this", "ROLE");
when(this.authenticationConverter.convert(any())).thenReturn(authentication);
when(this.authenticationManager.authenticate(any())).thenThrow(new BadCredentialsException("failed"));
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void filterWhenConvertEmptyThenOk() throws Exception {
when(this.authenticationConverter.convert(any())).thenReturn(null);
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, new MockHttpServletResponse(), chain);
verifyZeroInteractions(this.authenticationManagerResolver);
verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void filterWhenConvertAndAuthenticationSuccessThenSuccess() throws Exception {
Authentication authentication = new TestingAuthenticationToken("test", "this", "ROLE_USER");
when(this.authenticationConverter.convert(any())).thenReturn(authentication);
when(this.authenticationManager.authenticate(any())).thenReturn(authentication);
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
filter.setSuccessHandler(successHandler);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(this.successHandler).onAuthenticationSuccess(any(), any(), any(), eq(authentication));
verifyZeroInteractions(this.failureHandler);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
}
@Test(expected = ServletException.class)
public void filterWhenConvertAndAuthenticationEmptyThenServerError() throws Exception {
Authentication authentication = new TestingAuthenticationToken("test", "this", "ROLE_USER");
when(this.authenticationConverter.convert(any())).thenReturn(authentication);
when(this.authenticationManager.authenticate(any())).thenReturn(null);
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
filter.setSuccessHandler(successHandler);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
try {
filter.doFilter(request, response, chain);
} catch (ServletException e) {
verifyZeroInteractions(this.successHandler);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
throw e;
}
}
@Test
public void filterWhenNotMatchAndConvertAndAuthenticationSuccessThenContinues() throws Exception {
when(this.requestMatcher.matches(any())).thenReturn(false);
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManagerResolver, this.authenticationConverter);
filter.setRequestMatcher(this.requestMatcher);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verifyZeroInteractions(this.authenticationConverter, this.authenticationManagerResolver, this.successHandler);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2002-2019 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.web.authentication.www;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
/**
* @author Sergey Bespalov
* @since 5.2.0
*/
@RunWith(MockitoJUnitRunner.class)
public class BasicAuthenticationConverterTests {
@Mock
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
private BasicAuthenticationConverter converter;
@Before
public void setup() {
converter = new BasicAuthenticationConverter(authenticationDetailsSource);
}
@Test
public void testNormalOperation() throws Exception {
String token = "rod:koala";
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
UsernamePasswordAuthenticationToken authentication = converter.convert(request);
verify(authenticationDetailsSource).buildDetails(any());
assertThat(authentication).isNotNull();
assertThat(authentication.getName()).isEqualTo("rod");
}
@Test
public void testWhenUnsupportedAuthorizationHeaderThenIgnored() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Authorization", "Bearer someOtherToken");
UsernamePasswordAuthenticationToken authentication = converter.convert(request);
verifyZeroInteractions(authenticationDetailsSource);
assertThat(authentication).isNull();
}
@Test(expected = BadCredentialsException.class)
public void testWhenInvalidBasicAuthorizationTokenThenError() 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())));
converter.convert(request);
}
@Test(expected = BadCredentialsException.class)
public void testWhenInvalidBase64ThenError() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Authorization", "Basic NOT_VALID_BASE64");
converter.convert(request);
}
}