2021-09-28 16:30:17 -07:00
|
|
|
package auth.sso.oidc.custom;
|
|
|
|
|
2022-04-26 17:01:18 -04:00
|
|
|
import com.nimbusds.oauth2.sdk.AuthorizationGrant;
|
2021-09-28 16:30:17 -07:00
|
|
|
import com.nimbusds.oauth2.sdk.AuthorizationCode;
|
|
|
|
import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
|
|
|
|
import com.nimbusds.oauth2.sdk.ParseException;
|
|
|
|
import com.nimbusds.oauth2.sdk.TokenErrorResponse;
|
|
|
|
import com.nimbusds.oauth2.sdk.TokenRequest;
|
|
|
|
import com.nimbusds.oauth2.sdk.TokenResponse;
|
|
|
|
import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
|
|
|
|
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
|
|
|
|
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
|
|
|
|
import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
|
|
|
|
import com.nimbusds.oauth2.sdk.auth.Secret;
|
|
|
|
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
|
|
|
|
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
|
|
|
|
import com.nimbusds.oauth2.sdk.id.ClientID;
|
|
|
|
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
|
|
|
|
import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
|
|
|
|
import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URISyntaxException;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Optional;
|
|
|
|
import org.pac4j.core.context.WebContext;
|
|
|
|
import org.pac4j.core.credentials.authenticator.Authenticator;
|
|
|
|
import org.pac4j.core.exception.TechnicalException;
|
|
|
|
import org.pac4j.core.util.CommonHelper;
|
|
|
|
import org.pac4j.oidc.client.OidcClient;
|
|
|
|
import org.pac4j.oidc.config.OidcConfiguration;
|
|
|
|
import org.pac4j.oidc.credentials.OidcCredentials;
|
|
|
|
import org.pac4j.oidc.credentials.authenticator.OidcAuthenticator;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
|
|
|
|
public class CustomOidcAuthenticator implements Authenticator<OidcCredentials> {
|
|
|
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(OidcAuthenticator.class);
|
|
|
|
|
|
|
|
private static final Collection<ClientAuthenticationMethod> SUPPORTED_METHODS =
|
|
|
|
Arrays.asList(
|
|
|
|
ClientAuthenticationMethod.CLIENT_SECRET_POST,
|
|
|
|
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
|
|
|
|
ClientAuthenticationMethod.NONE);
|
|
|
|
|
|
|
|
protected OidcConfiguration configuration;
|
|
|
|
|
|
|
|
protected OidcClient client;
|
|
|
|
|
|
|
|
private ClientAuthentication clientAuthentication;
|
|
|
|
|
|
|
|
public CustomOidcAuthenticator(final OidcConfiguration configuration, final OidcClient client) {
|
|
|
|
CommonHelper.assertNotNull("configuration", configuration);
|
|
|
|
CommonHelper.assertNotNull("client", client);
|
|
|
|
this.configuration = configuration;
|
|
|
|
this.client = client;
|
|
|
|
|
|
|
|
// check authentication methods
|
|
|
|
final List<ClientAuthenticationMethod> metadataMethods = configuration.findProviderMetadata().getTokenEndpointAuthMethods();
|
|
|
|
|
|
|
|
final ClientAuthenticationMethod preferredMethod = getPreferredAuthenticationMethod(configuration);
|
|
|
|
|
|
|
|
final ClientAuthenticationMethod chosenMethod;
|
|
|
|
if (CommonHelper.isNotEmpty(metadataMethods)) {
|
|
|
|
if (preferredMethod != null) {
|
2022-04-26 17:01:18 -04:00
|
|
|
if (ClientAuthenticationMethod.NONE.equals(preferredMethod) || metadataMethods.contains(preferredMethod)) {
|
2021-09-28 16:30:17 -07:00
|
|
|
chosenMethod = preferredMethod;
|
|
|
|
} else {
|
|
|
|
throw new TechnicalException(
|
2022-05-10 18:15:53 -05:00
|
|
|
"Preferred authentication method ("
|
|
|
|
+ preferredMethod
|
|
|
|
+ ") not supported "
|
|
|
|
+ "by provider according to provider metadata ("
|
|
|
|
+ metadataMethods
|
|
|
|
+ ").");
|
2021-09-28 16:30:17 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
chosenMethod = firstSupportedMethod(metadataMethods);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
chosenMethod = preferredMethod != null ? preferredMethod : ClientAuthenticationMethod.getDefault();
|
|
|
|
logger.info("Provider metadata does not provide Token endpoint authentication methods. Using: {}",
|
|
|
|
chosenMethod);
|
|
|
|
}
|
|
|
|
|
2022-05-10 18:15:53 -05:00
|
|
|
final ClientID clientID = new ClientID(configuration.getClientId());
|
2022-04-26 17:01:18 -04:00
|
|
|
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(chosenMethod)) {
|
2022-05-10 18:15:53 -05:00
|
|
|
final Secret secret = new Secret(configuration.getSecret());
|
|
|
|
clientAuthentication = new ClientSecretPost(clientID, secret);
|
2021-09-28 16:30:17 -07:00
|
|
|
} else if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(chosenMethod)) {
|
2022-05-10 18:15:53 -05:00
|
|
|
final Secret secret = new Secret(configuration.getSecret());
|
|
|
|
clientAuthentication = new ClientSecretBasic(clientID, secret);
|
2022-04-26 17:01:18 -04:00
|
|
|
} else if (ClientAuthenticationMethod.NONE.equals(chosenMethod)) {
|
|
|
|
clientAuthentication = null; // No client authentication in none mode
|
2021-09-28 16:30:17 -07:00
|
|
|
} else {
|
|
|
|
throw new TechnicalException("Unsupported client authentication method: " + chosenMethod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The preferred {@link ClientAuthenticationMethod} specified in the given
|
|
|
|
* {@link OidcConfiguration}, or <code>null</code> meaning that the a
|
|
|
|
* provider-supported method should be chosen.
|
|
|
|
*/
|
|
|
|
private static ClientAuthenticationMethod getPreferredAuthenticationMethod(OidcConfiguration config) {
|
|
|
|
final ClientAuthenticationMethod configurationMethod = config.getClientAuthenticationMethod();
|
|
|
|
if (configurationMethod == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!SUPPORTED_METHODS.contains(configurationMethod)) {
|
|
|
|
throw new TechnicalException("Configured authentication method (" + configurationMethod + ") is not supported.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return configurationMethod;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The first {@link ClientAuthenticationMethod} from the given list of
|
|
|
|
* methods that is supported by this implementation.
|
|
|
|
*
|
|
|
|
* @throws TechnicalException
|
|
|
|
* if none of the provider-supported methods is supported.
|
|
|
|
*/
|
|
|
|
private static ClientAuthenticationMethod firstSupportedMethod(final List<ClientAuthenticationMethod> metadataMethods) {
|
|
|
|
Optional<ClientAuthenticationMethod> firstSupported =
|
|
|
|
metadataMethods.stream().filter((m) -> SUPPORTED_METHODS.contains(m)).findFirst();
|
|
|
|
if (firstSupported.isPresent()) {
|
|
|
|
return firstSupported.get();
|
|
|
|
} else {
|
2022-05-10 18:15:53 -05:00
|
|
|
throw new TechnicalException("None of the Token endpoint provider metadata authentication methods are supported: "
|
|
|
|
+ metadataMethods);
|
2021-09-28 16:30:17 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void validate(final OidcCredentials credentials, final WebContext context) {
|
|
|
|
final AuthorizationCode code = credentials.getCode();
|
|
|
|
// if we have a code
|
|
|
|
if (code != null) {
|
|
|
|
try {
|
|
|
|
final String computedCallbackUrl = client.computeFinalCallbackUrl(context);
|
|
|
|
// Token request
|
2022-04-26 17:01:18 -04:00
|
|
|
final TokenRequest request = createTokenRequest(new AuthorizationCodeGrant(code, new URI(computedCallbackUrl)));
|
2021-09-28 16:30:17 -07:00
|
|
|
HTTPRequest tokenHttpRequest = request.toHTTPRequest();
|
|
|
|
tokenHttpRequest.setConnectTimeout(configuration.getConnectTimeout());
|
|
|
|
tokenHttpRequest.setReadTimeout(configuration.getReadTimeout());
|
|
|
|
|
|
|
|
final HTTPResponse httpResponse = tokenHttpRequest.send();
|
|
|
|
logger.debug("Token response: status={}, content={}", httpResponse.getStatusCode(),
|
|
|
|
httpResponse.getContent());
|
|
|
|
|
|
|
|
final TokenResponse response = OIDCTokenResponseParser.parse(httpResponse);
|
|
|
|
if (response instanceof TokenErrorResponse) {
|
|
|
|
throw new TechnicalException("Bad token response, error=" + ((TokenErrorResponse) response).getErrorObject());
|
|
|
|
}
|
|
|
|
logger.debug("Token response successful");
|
|
|
|
final OIDCTokenResponse tokenSuccessResponse = (OIDCTokenResponse) response;
|
|
|
|
|
|
|
|
// save tokens in credentials
|
|
|
|
final OIDCTokens oidcTokens = tokenSuccessResponse.getOIDCTokens();
|
|
|
|
credentials.setAccessToken(oidcTokens.getAccessToken());
|
|
|
|
credentials.setRefreshToken(oidcTokens.getRefreshToken());
|
|
|
|
credentials.setIdToken(oidcTokens.getIDToken());
|
|
|
|
|
|
|
|
} catch (final URISyntaxException | IOException | ParseException e) {
|
|
|
|
throw new TechnicalException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 17:01:18 -04:00
|
|
|
private TokenRequest createTokenRequest(final AuthorizationGrant grant) {
|
|
|
|
if (clientAuthentication != null) {
|
|
|
|
return new TokenRequest(configuration.findProviderMetadata().getTokenEndpointURI(),
|
|
|
|
this.clientAuthentication, grant);
|
|
|
|
} else {
|
|
|
|
return new TokenRequest(configuration.findProviderMetadata().getTokenEndpointURI(),
|
|
|
|
new ClientID(configuration.getClientId()), grant);
|
|
|
|
}
|
2021-09-28 16:30:17 -07:00
|
|
|
}
|
|
|
|
}
|