Polish gh-72
This commit is contained in:
parent
09a363ba67
commit
7a5585f1fc
@ -26,17 +26,22 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AuthenticationProvider} implementation that validates {@link OAuth2ClientAuthenticationToken}s.
|
* An {@link AuthenticationProvider} implementation that validates {@link OAuth2ClientAuthenticationToken}'s.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see AuthenticationProvider
|
||||||
|
* @see OAuth2ClientAuthenticationToken
|
||||||
|
* @see RegisteredClientRepository
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationProvider implements AuthenticationProvider {
|
public class OAuth2ClientAuthenticationProvider implements AuthenticationProvider {
|
||||||
private final RegisteredClientRepository registeredClientRepository;
|
private final RegisteredClientRepository registeredClientRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param registeredClientRepository
|
* Constructs an {@code OAuth2ClientAuthenticationProvider} using the provided parameters.
|
||||||
* the bean to lookup the client details from
|
*
|
||||||
|
* @param registeredClientRepository the repository of registered clients
|
||||||
*/
|
*/
|
||||||
public OAuth2ClientAuthenticationProvider(RegisteredClientRepository registeredClientRepository) {
|
public OAuth2ClientAuthenticationProvider(RegisteredClientRepository registeredClientRepository) {
|
||||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||||
@ -45,21 +50,14 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
String clientId = authentication.getName();
|
String clientId = authentication.getPrincipal().toString();
|
||||||
if (authentication.getCredentials() == null) {
|
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
||||||
// https://tools.ietf.org/html/rfc6749#section-2.4
|
|
||||||
if (registeredClient == null) {
|
if (registeredClient == null) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
String presentedSecret = authentication.getCredentials()
|
String clientSecret = authentication.getCredentials().toString();
|
||||||
.toString();
|
if (!registeredClient.getClientSecret().equals(clientSecret)) {
|
||||||
if (!registeredClient.getClientSecret()
|
|
||||||
.equals(presentedSecret)) {
|
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,40 +15,75 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An {@link Authentication} implementation used for OAuth 2.0 Client Authentication.
|
||||||
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see AbstractAuthenticationToken
|
||||||
|
* @see RegisteredClient
|
||||||
|
* @see OAuth2ClientAuthenticationProvider
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken {
|
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
private RegisteredClient registeredClient;
|
private RegisteredClient registeredClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2ClientAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param clientId the client identifier
|
||||||
|
* @param clientSecret the client secret
|
||||||
|
*/
|
||||||
public OAuth2ClientAuthenticationToken(String clientId, String clientSecret) {
|
public OAuth2ClientAuthenticationToken(String clientId, String clientSecret) {
|
||||||
super(Collections.emptyList());
|
super(Collections.emptyList());
|
||||||
|
Assert.hasText(clientId, "clientId cannot be empty");
|
||||||
|
Assert.hasText(clientSecret, "clientSecret cannot be empty");
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.clientSecret = clientSecret;
|
this.clientSecret = clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2ClientAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param registeredClient the registered client
|
||||||
|
*/
|
||||||
public OAuth2ClientAuthenticationToken(RegisteredClient registeredClient) {
|
public OAuth2ClientAuthenticationToken(RegisteredClient registeredClient) {
|
||||||
super(Collections.emptyList());
|
super(Collections.emptyList());
|
||||||
|
Assert.notNull(registeredClient, "registeredClient cannot be null");
|
||||||
this.registeredClient = registeredClient;
|
this.registeredClient = registeredClient;
|
||||||
setAuthenticated(true);
|
setAuthenticated(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.registeredClient != null ?
|
||||||
|
this.registeredClient.getClientId() :
|
||||||
|
this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getCredentials() {
|
public Object getCredentials() {
|
||||||
return this.clientSecret;
|
return this.clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public Object getPrincipal() {
|
* Returns the {@link RegisteredClient registered client}.
|
||||||
return this.clientId;
|
*
|
||||||
|
* @return the {@link RegisteredClient}
|
||||||
|
*/
|
||||||
|
public @Nullable RegisteredClient getRegisteredClient() {
|
||||||
|
return this.registeredClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,57 +16,72 @@
|
|||||||
package org.springframework.security.oauth2.server.authorization.web;
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts from {@link HttpServletRequest} to {@link OAuth2ClientAuthenticationToken} that can be authenticated.
|
* Attempts to extract HTTP Basic credentials from {@link HttpServletRequest}
|
||||||
|
* and then converts to an {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
|
||||||
*
|
*
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see OAuth2ClientAuthenticationToken
|
||||||
|
* @see OAuth2ClientAuthenticationFilter
|
||||||
*/
|
*/
|
||||||
public class DefaultOAuth2ClientAuthenticationConverter implements AuthenticationConverter {
|
public class ClientSecretBasicAuthenticationConverter implements AuthenticationConverter {
|
||||||
|
|
||||||
private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OAuth2ClientAuthenticationToken convert(HttpServletRequest request) {
|
public Authentication convert(HttpServletRequest request) {
|
||||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
|
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
header = header.trim();
|
String[] parts = header.split("\\s");
|
||||||
if (!StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) {
|
if (!parts[0].equalsIgnoreCase("Basic")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
|
if (parts.length != 2) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] decoded;
|
byte[] decodedCredentials;
|
||||||
try {
|
try {
|
||||||
byte[] base64Token = header.substring(6)
|
decodedCredentials = Base64.getDecoder().decode(
|
||||||
.getBytes(StandardCharsets.UTF_8);
|
parts[1].getBytes(StandardCharsets.UTF_8));
|
||||||
decoded = Base64.getDecoder()
|
} catch (IllegalArgumentException ex) {
|
||||||
.decode(base64Token);
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
|
||||||
} catch (IllegalArgumentException e) {
|
}
|
||||||
|
|
||||||
|
String credentialsString = new String(decodedCredentials, StandardCharsets.UTF_8);
|
||||||
|
String[] credentials = credentialsString.split(":", 2);
|
||||||
|
if (credentials.length != 2 ||
|
||||||
|
!StringUtils.hasText(credentials[0]) ||
|
||||||
|
!StringUtils.hasText(credentials[1])) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = new String(decoded, StandardCharsets.UTF_8);
|
String clientID;
|
||||||
String[] credentials = token.split(":");
|
String clientSecret;
|
||||||
if (credentials.length != 2) {
|
try {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN));
|
clientID = URLDecoder.decode(credentials[0], StandardCharsets.UTF_8.name());
|
||||||
|
clientSecret = URLDecoder.decode(credentials[1], StandardCharsets.UTF_8.name());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
|
||||||
}
|
}
|
||||||
return new OAuth2ClientAuthenticationToken(credentials[0], credentials[1]);
|
|
||||||
|
return new OAuth2ClientAuthenticationToken(clientID, clientSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,21 +15,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization.web;
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.server.ServletServerHttpResponse;
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -37,54 +43,29 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A {@code Filter} that processes an authentication request for an OAuth 2.0 Client.
|
||||||
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @since 0.0.1
|
||||||
|
* @see AuthenticationManager
|
||||||
|
* @see OAuth2ClientAuthenticationProvider
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-2.3">Section 2.3 Client Authentication</a>
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-3.2.1">Section 3.2.1 Token Endpoint Client Authentication</a>
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
public static final String DEFAULT_FILTER_PROCESSES_URL = "/oauth2/token";
|
|
||||||
private final AuthenticationManager authenticationManager;
|
private final AuthenticationManager authenticationManager;
|
||||||
private final RequestMatcher requestMatcher;
|
private final RequestMatcher requestMatcher;
|
||||||
private final OAuth2ErrorHttpMessageConverter errorMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
|
||||||
|
private AuthenticationConverter authenticationConverter;
|
||||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||||
private AuthenticationFailureHandler authenticationFailureHandler;
|
private AuthenticationFailureHandler authenticationFailureHandler;
|
||||||
private AuthenticationConverter authenticationConverter = new DefaultOAuth2ClientAuthenticationConverter();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance which will authenticate against the supplied
|
* Constructs an {@code OAuth2ClientAuthenticationFilter} using the provided parameters.
|
||||||
* {@code AuthenticationManager}.
|
|
||||||
*
|
*
|
||||||
* @param authenticationManager
|
* @param authenticationManager the {@link AuthenticationManager} used for authenticating the client
|
||||||
* the bean to submit authentication requests to
|
* @param requestMatcher the {@link RequestMatcher} used for matching against the {@code HttpServletRequest}
|
||||||
*/
|
|
||||||
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager) {
|
|
||||||
this(authenticationManager, DEFAULT_FILTER_PROCESSES_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance which will authenticate against the supplied
|
|
||||||
* {@code AuthenticationManager}.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Configures default {@link RequestMatcher} verifying the provided endpoint.
|
|
||||||
*
|
|
||||||
* @param authenticationManager
|
|
||||||
* the bean to submit authentication requests to
|
|
||||||
* @param filterProcessesUrl
|
|
||||||
* the filterProcessesUrl to match request URI against
|
|
||||||
*/
|
|
||||||
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager, String filterProcessesUrl) {
|
|
||||||
this(authenticationManager, new AntPathRequestMatcher(filterProcessesUrl, "POST"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance which will authenticate against the supplied
|
|
||||||
* {@code AuthenticationManager} and custom {@code RequestMatcher}.
|
|
||||||
*
|
|
||||||
* @param authenticationManager
|
|
||||||
* the bean to submit authentication requests to
|
|
||||||
* @param requestMatcher
|
|
||||||
* the {@code RequestMatcher} to match {@code HttpServletRequest} against
|
|
||||||
*/
|
*/
|
||||||
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager,
|
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager,
|
||||||
RequestMatcher requestMatcher) {
|
RequestMatcher requestMatcher) {
|
||||||
@ -92,8 +73,9 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||||
this.authenticationManager = authenticationManager;
|
this.authenticationManager = authenticationManager;
|
||||||
this.requestMatcher = requestMatcher;
|
this.requestMatcher = requestMatcher;
|
||||||
this.authenticationSuccessHandler = this::defaultAuthenticationSuccessHandler;
|
this.authenticationConverter = new ClientSecretBasicAuthenticationConverter();
|
||||||
this.authenticationFailureHandler = this::defaultAuthenticationFailureHandler;
|
this.authenticationSuccessHandler = this::onAuthenticationSuccess;
|
||||||
|
this.authenticationFailureHandler = this::onAuthenticationFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -101,14 +83,12 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
if (this.requestMatcher.matches(request)) {
|
if (this.requestMatcher.matches(request)) {
|
||||||
Authentication authentication = this.authenticationConverter.convert(request);
|
|
||||||
if (authentication == null) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
final Authentication result = this.authenticationManager.authenticate(authentication);
|
Authentication authenticationRequest = this.authenticationConverter.convert(request);
|
||||||
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, result);
|
if (authenticationRequest != null) {
|
||||||
|
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
|
||||||
|
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
|
||||||
|
}
|
||||||
} catch (OAuth2AuthenticationException failed) {
|
} catch (OAuth2AuthenticationException failed) {
|
||||||
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
|
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
|
||||||
return;
|
return;
|
||||||
@ -118,10 +98,19 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to define custom behaviour on a successful authentication.
|
* Sets the {@link AuthenticationConverter} used for converting a {@link HttpServletRequest} to an {@link OAuth2ClientAuthenticationToken}.
|
||||||
*
|
*
|
||||||
* @param authenticationSuccessHandler
|
* @param authenticationConverter used for converting a {@link HttpServletRequest} to an {@link OAuth2ClientAuthenticationToken}
|
||||||
* the handler to be used
|
*/
|
||||||
|
public final void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
|
||||||
|
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||||
|
this.authenticationConverter = authenticationConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link AuthenticationSuccessHandler} used for handling successful authentications.
|
||||||
|
*
|
||||||
|
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling successful authentications
|
||||||
*/
|
*/
|
||||||
public final void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
|
public final void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
|
||||||
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
|
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
|
||||||
@ -129,39 +118,43 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to define custom behaviour on a failed authentication.
|
* Sets the {@link AuthenticationFailureHandler} used for handling failed authentications.
|
||||||
*
|
*
|
||||||
* @param authenticationFailureHandler
|
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling failed authentications
|
||||||
* the handler to be used
|
|
||||||
*/
|
*/
|
||||||
public final void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
|
public final void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
|
||||||
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
|
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
|
||||||
this.authenticationFailureHandler = authenticationFailureHandler;
|
this.authenticationFailureHandler = authenticationFailureHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
* Used to define custom {@link AuthenticationConverter}.
|
|
||||||
*
|
|
||||||
* @param authenticationConverter
|
|
||||||
* the converter to be used
|
|
||||||
*/
|
|
||||||
public final void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
|
|
||||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
|
||||||
this.authenticationConverter = authenticationConverter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defaultAuthenticationSuccessHandler(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
Authentication authentication) {
|
Authentication authentication) {
|
||||||
|
|
||||||
SecurityContextHolder.getContext()
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
.setAuthentication(authentication);
|
context.setAuthentication(authentication);
|
||||||
|
SecurityContextHolder.setContext(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void defaultAuthenticationFailureHandler(HttpServletRequest request, HttpServletResponse response,
|
private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||||
AuthenticationException failed) throws IOException {
|
AuthenticationException failed) throws IOException {
|
||||||
|
|
||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
this.errorMessageConverter.write(((OAuth2AuthenticationException) failed).getError(),
|
|
||||||
MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
|
// TODO
|
||||||
|
// The authorization server MAY return an HTTP 401 (Unauthorized) status code
|
||||||
|
// to indicate which HTTP authentication schemes are supported.
|
||||||
|
// If the client attempted to authenticate via the "Authorization" request header field,
|
||||||
|
// the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and
|
||||||
|
// include the "WWW-Authenticate" response header field
|
||||||
|
// matching the authentication scheme used by the client.
|
||||||
|
|
||||||
|
OAuth2Error error = ((OAuth2AuthenticationException) failed).getError();
|
||||||
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
|
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
|
||||||
|
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
} else {
|
||||||
|
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
this.errorHttpResponseConverter.write(error, null, httpResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,12 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
import java.util.Collections;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
@ -30,25 +31,25 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||||||
* Tests for {@link OAuth2ClientAuthenticationProvider}.
|
* Tests for {@link OAuth2ClientAuthenticationProvider}.
|
||||||
*
|
*
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationProviderTests {
|
public class OAuth2ClientAuthenticationProviderTests {
|
||||||
|
|
||||||
private RegisteredClient registeredClient;
|
private RegisteredClient registeredClient;
|
||||||
private RegisteredClientRepository registeredClientRepository;
|
private RegisteredClientRepository registeredClientRepository;
|
||||||
private OAuth2ClientAuthenticationProvider authenticationProvider;
|
private OAuth2ClientAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.registeredClient = TestRegisteredClients.registeredClient()
|
this.registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
.build();
|
|
||||||
this.registeredClientRepository = new InMemoryRegisteredClientRepository(this.registeredClient);
|
this.registeredClientRepository = new InMemoryRegisteredClientRepository(this.registeredClient);
|
||||||
this.authenticationProvider = new OAuth2ClientAuthenticationProvider(this.registeredClientRepository);
|
this.authenticationProvider = new OAuth2ClientAuthenticationProvider(this.registeredClientRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() {
|
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new OAuth2ClientAuthenticationProvider(null)).isInstanceOf(
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationProvider(null))
|
||||||
IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("registeredClientRepository cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -57,34 +58,36 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenNullCredentialsThenThrowOAuth2AuthorizationException() {
|
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() {
|
||||||
assertThatThrownBy(() -> {
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||||
this.authenticationProvider.authenticate(new OAuth2ClientAuthenticationToken("id", null));
|
this.registeredClient.getClientId() + "-invalid", this.registeredClient.getClientSecret());
|
||||||
}).isInstanceOf(OAuth2AuthenticationException.class);
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenNullRegisteredClientThenThrowOAuth2AuthorizationException() {
|
public void authenticateWhenInvalidClientSecretThenThrowOAuth2AuthenticationException() {
|
||||||
assertThatThrownBy(() -> {
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||||
this.authenticationProvider.authenticate(new OAuth2ClientAuthenticationToken("id", "secret"));
|
this.registeredClient.getClientId(), this.registeredClient.getClientSecret() + "-invalid");
|
||||||
}).isInstanceOf(OAuth2AuthenticationException.class);
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenCredentialsNotEqualThenThrowOAuth2AuthorizationException() {
|
public void authenticateWhenValidCredentialsThenAuthenticated() {
|
||||||
assertThatThrownBy(() -> {
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||||
this.authenticationProvider.authenticate(
|
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
||||||
new OAuth2ClientAuthenticationToken(this.registeredClient.getClientId(),
|
OAuth2ClientAuthenticationToken authenticationResult =
|
||||||
this.registeredClient.getClientSecret() + "_invalid"));
|
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
}).isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenAuthenticationSuccessResponseThenReturnClientAuthenticationToken() {
|
|
||||||
OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2ClientAuthenticationToken(this.registeredClient.getClientId(),
|
|
||||||
registeredClient.getClientSecret()));
|
|
||||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
assertThat(authenticationResult.getAuthorities()).isEqualTo(Collections.emptyList());
|
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(this.registeredClient.getClientId());
|
||||||
|
assertThat(authenticationResult.getCredentials()).isNull();
|
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(this.registeredClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2ClientAuthenticationToken}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2ClientAuthenticationTokenTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, "secret"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("clientId cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientSecretNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("clientSecret cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenRegisteredClientNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("registeredClient cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientCredentialsProvidedThenCreated() {
|
||||||
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", "secret");
|
||||||
|
assertThat(authentication.isAuthenticated()).isFalse();
|
||||||
|
assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId");
|
||||||
|
assertThat(authentication.getCredentials()).isEqualTo("secret");
|
||||||
|
assertThat(authentication.getRegisteredClient()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenRegisteredClientProvidedThenCreated() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
assertThat(authentication.isAuthenticated()).isTrue();
|
||||||
|
assertThat(authentication.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||||
|
assertThat(authentication.getCredentials()).isNull();
|
||||||
|
assertThat(authentication.getRegisteredClient()).isEqualTo(registeredClient);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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.oauth2.server.authorization.web;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ClientSecretBasicAuthenticationConverter}.
|
||||||
|
*
|
||||||
|
* @author Patryk Kostrzewa
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class ClientSecretBasicAuthenticationConverterTests {
|
||||||
|
private ClientSecretBasicAuthenticationConverter converter = new ClientSecretBasicAuthenticationConverter();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertWhenAuthorizationHeaderEmptyThenReturnNull() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
Authentication authentication = this.converter.convert(request);
|
||||||
|
assertThat(authentication).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertWhenAuthorizationHeaderNotBasicThenReturnNull() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer token");
|
||||||
|
Authentication authentication = this.converter.convert(request);
|
||||||
|
assertThat(authentication).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertWhenAuthorizationHeaderBasicWithMissingCredentialsThenThrowOAuth2AuthenticationException() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic ");
|
||||||
|
assertThatThrownBy(() -> this.converter.convert(request))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertWhenAuthorizationHeaderBasicWithInvalidBase64ThenThrowOAuth2AuthenticationException() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic clientId:secret");
|
||||||
|
assertThatThrownBy(() -> this.converter.convert(request))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertWhenAuthorizationHeaderBasicWithMissingSecretThenThrowOAuth2AuthenticationException() throws Exception {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", ""));
|
||||||
|
assertThatThrownBy(() -> this.converter.convert(request))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertWhenAuthorizationHeaderBasicWithValidCredentialsThenReturnClientAuthenticationToken() throws Exception {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", "secret"));
|
||||||
|
OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo("clientId");
|
||||||
|
assertThat(authentication.getCredentials()).isEqualTo("secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||||
|
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||||
|
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||||
|
String credentialsString = clientId + ":" + secret;
|
||||||
|
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return new String(encodedBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 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.oauth2.server.authorization.web;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import java.util.Base64;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link DefaultOAuth2ClientAuthenticationConverter}.
|
|
||||||
*
|
|
||||||
* @author Patryk Kostrzewa
|
|
||||||
*/
|
|
||||||
public class DefaultOAuth2ClientAuthenticationConverterTests {
|
|
||||||
|
|
||||||
private DefaultOAuth2ClientAuthenticationConverter converter;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.converter = new DefaultOAuth2ClientAuthenticationConverter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertWhenConversionSuccessThenReturnClientAuthenticationToken() {
|
|
||||||
String token = "client:secret";
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder()
|
|
||||||
.encodeToString(token.getBytes()));
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken authentication = this.converter.convert(request);
|
|
||||||
|
|
||||||
assertThat(authentication).isNotNull();
|
|
||||||
assertThat(authentication.getName()).isEqualTo("client");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertWithAuthorizationSchemeInMixedCaseWhenConversionSuccessThenReturnClientAuthenticationToken() {
|
|
||||||
String token = "client:secret";
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addHeader(HttpHeaders.AUTHORIZATION, "BaSiC " + Base64.getEncoder()
|
|
||||||
.encodeToString(token.getBytes()));
|
|
||||||
|
|
||||||
final OAuth2ClientAuthenticationToken authentication = this.converter.convert(request);
|
|
||||||
|
|
||||||
assertThat(authentication).isNotNull();
|
|
||||||
assertThat(authentication.getName()).isEqualTo("client");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertWithIgnoringUnsupportedAuthenticationHeaderWhenConversionSuccessThenReturnClientAuthenticationToken() {
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer unsupportedToken");
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken authentication = this.converter.convert(request);
|
|
||||||
|
|
||||||
assertThat(authentication).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertWhenNotValidTokenThenThrowOAuth2AuthenticationException() {
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder()
|
|
||||||
.encodeToString("client".getBytes()));
|
|
||||||
assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertWhenNotValidBase64ThenThrowOAuth2AuthenticationException() {
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic NOT_VALID_BASE64");
|
|
||||||
assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertWhenEmptyAuthenticationHeaderTokenThenThrowOAuth2AuthenticationException() {
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic ");
|
|
||||||
assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,85 +15,197 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization.web;
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2ClientAuthenticationFilter}.
|
* Tests for {@link OAuth2ClientAuthenticationFilter}.
|
||||||
*
|
*
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationFilterTests {
|
public class OAuth2ClientAuthenticationFilterTests {
|
||||||
|
private String filterProcessesUrl = "/oauth2/token";
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
private RequestMatcher requestMatcher;
|
||||||
|
private AuthenticationConverter authenticationConverter;
|
||||||
private OAuth2ClientAuthenticationFilter filter;
|
private OAuth2ClientAuthenticationFilter filter;
|
||||||
private AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
|
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||||
private RequestMatcher requestMatcher = mock(RequestMatcher.class);
|
new OAuth2ErrorHttpMessageConverter();
|
||||||
private FilterChain filterChain = mock(FilterChain.class);
|
|
||||||
private String filterProcessesUrl;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.filterProcessesUrl = "/oauth2/token";
|
this.authenticationManager = mock(AuthenticationManager.class);
|
||||||
this.filter = new OAuth2ClientAuthenticationFilter(authenticationManager, requestMatcher);
|
this.requestMatcher = new AntPathRequestMatcher(this.filterProcessesUrl, HttpMethod.POST.name());
|
||||||
|
this.filter = new OAuth2ClientAuthenticationFilter(this.authenticationManager, this.requestMatcher);
|
||||||
|
this.authenticationConverter = mock(AuthenticationConverter.class);
|
||||||
|
this.filter.setAuthenticationConverter(this.authenticationConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanup() {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorManagerAndMatcherWhenNullThenThrowIllegalArgumentException() {
|
public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> {
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(null, this.requestMatcher))
|
||||||
new OAuth2ClientAuthenticationFilter(null, (RequestMatcher) null);
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
}).isInstanceOf(IllegalArgumentException.class);
|
.hasMessage("authenticationManager cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorManagerAndFilterUrlWhenNullThenThrowIllegalArgumentException() {
|
public void constructorWhenRequestMatcherNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> {
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(this.authenticationManager, null))
|
||||||
new OAuth2ClientAuthenticationFilter(null, (String) null);
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
}).isInstanceOf(IllegalArgumentException.class);
|
.hasMessage("requestMatcher cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenNotTokenRequestThenNextFilter() throws Exception {
|
public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() {
|
||||||
this.filterProcessesUrl = "/path";
|
assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null))
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
request.setServletPath(this.filterProcessesUrl);
|
.hasMessage("authenticationConverter cannot be null");
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
this.filter.doFilter(request, response, this.filterChain);
|
|
||||||
|
|
||||||
verify(this.filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthenticationRequestGetThenNotProcessed() throws Exception {
|
public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||||
String requestUri = OAuth2ClientAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URL;
|
assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null))
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authenticationSuccessHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authenticationFailureHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestDoesNotMatchThenNotProcessed() throws Exception {
|
||||||
|
String requestUri = "/path";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||||
request.setServletPath(requestUri);
|
request.setServletPath(requestUri);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, this.filterChain);
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
verify(this.filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthenticationIsNullThenNotProcessed() throws Exception {
|
public void doFilterWhenRequestMatchesAndEmptyCredentialsThenNotProcessed() throws Exception {
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
||||||
request.setServletPath(this.filterProcessesUrl);
|
request.setServletPath(this.filterProcessesUrl);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, this.filterChain);
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
verify(this.filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestMatchesAndInvalidCredentialsThenInvalidRequestError() throws Exception {
|
||||||
|
when(this.authenticationConverter.convert(any(HttpServletRequest.class))).thenThrow(
|
||||||
|
new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST)));
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
||||||
|
request.setServletPath(this.filterProcessesUrl);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verifyNoInteractions(filterChain);
|
||||||
|
|
||||||
|
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
|
||||||
|
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
|
||||||
|
OAuth2Error error = readError(response);
|
||||||
|
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestMatchesAndBadCredentialsThenInvalidClientError() throws Exception {
|
||||||
|
when(this.authenticationConverter.convert(any(HttpServletRequest.class))).thenReturn(
|
||||||
|
new OAuth2ClientAuthenticationToken("clientId", "invalid-secret"));
|
||||||
|
when(this.authenticationManager.authenticate(any(Authentication.class))).thenThrow(
|
||||||
|
new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT)));
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
||||||
|
request.setServletPath(this.filterProcessesUrl);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verifyNoInteractions(filterChain);
|
||||||
|
|
||||||
|
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
|
||||||
|
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
OAuth2Error error = readError(response);
|
||||||
|
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestMatchesAndValidCredentialsThenProcessed() throws Exception {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.authenticationConverter.convert(any(HttpServletRequest.class))).thenReturn(
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), registeredClient.getClientSecret()));
|
||||||
|
when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient));
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
||||||
|
request.setServletPath(this.filterProcessesUrl);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(authentication).isInstanceOf(OAuth2ClientAuthenticationToken.class);
|
||||||
|
assertThat(((OAuth2ClientAuthenticationToken) authentication).getRegisteredClient()).isEqualTo(registeredClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
|
||||||
|
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||||
|
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
|
||||||
|
return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user