issue/6506: AuthenticationConverter implementation
This commit is contained in:
parent
e584207a85
commit
f1187bdfc2
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ")) {
|
||||
try {
|
||||
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
|
||||
if (authRequest == null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String[] tokens = extractAndDecodeHeader(header, request);
|
||||
assert tokens.length == 2;
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue