Last active
July 30, 2023 07:44
-
-
Save alexramos1/329f8a927707c266e06bf41219f66570 to your computer and use it in GitHub Desktop.
Simplest possible implementation of AWS Cognito username/password authentication on Spring Security.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.nio.charset.StandardCharsets; | |
import java.util.ArrayList; | |
import java.util.Base64; | |
import java.util.HashMap; | |
import java.util.Map; | |
import javax.annotation.Nonnull; | |
import javax.crypto.Mac; | |
import javax.crypto.spec.SecretKeySpec; | |
import org.apache.commons.codec.digest.HmacAlgorithms; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.security.authentication.AuthenticationProvider; | |
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.core.AuthenticationException; | |
import org.springframework.stereotype.Component; | |
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; | |
import com.amazonaws.regions.Regions; | |
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider; | |
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClientBuilder; | |
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthRequest; | |
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthResult; | |
import com.amazonaws.services.cognitoidp.model.AuthFlowType; | |
@Component | |
public class CognitoAuthenticationProvider implements AuthenticationProvider { | |
@Value("${COGNITO_POOL_ID}") | |
private String poolId; | |
@Value("${COGNITO_CLIENT_ID}") | |
private String clientId; | |
@Value("${COGNITO_CLIENT_SECRET}") | |
private String clientSecret; | |
private DefaultAWSCredentialsProviderChain awsCredentialsProvider = new DefaultAWSCredentialsProviderChain(); | |
@Override | |
public Authentication authenticate(Authentication authentication) throws AuthenticationException { | |
String userId = authentication.getName(); | |
String password = authentication.getCredentials().toString(); | |
Map<String, String> params = new HashMap<>(); | |
params.put("USERNAME", userId); | |
params.put("SECRET_HASH", calculateSecretHash(userId)); | |
params.put("PASSWORD", password); | |
AdminInitiateAuthRequest request = new AdminInitiateAuthRequest() | |
.withUserPoolId(poolId) | |
.withClientId(clientId) | |
.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH) | |
.withAuthParameters(params); | |
AWSCognitoIdentityProvider identityProvider = AWSCognitoIdentityProviderClientBuilder.standard() | |
.withCredentials(awsCredentialsProvider).withRegion(Regions.US_WEST_2).build(); | |
AdminInitiateAuthResult result = identityProvider.adminInitiateAuth(request); | |
if(result.getChallengeName().equals("NEW_PASSWORD_REQUIRED")) { | |
// ignore | |
} | |
return new UsernamePasswordAuthenticationToken( | |
userId, password, new ArrayList<>()); | |
} | |
@Override | |
public boolean supports(Class<?> authentication) { | |
return authentication.equals(UsernamePasswordAuthenticationToken.class); | |
} | |
private String calculateSecretHash(@Nonnull String userName) { | |
SecretKeySpec signingKey = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), | |
HmacAlgorithms.HMAC_SHA_256.toString()); | |
try { | |
Mac mac = Mac.getInstance(HmacAlgorithms.HMAC_SHA_256.toString()); | |
mac.init(signingKey); | |
mac.update(userName.getBytes(StandardCharsets.UTF_8)); | |
byte[] rawHmac = mac.doFinal(clientId.getBytes(StandardCharsets.UTF_8)); | |
return Base64.getEncoder().encodeToString(rawHmac); | |
} catch (Exception ex) { | |
throw new RuntimeException("Error calculating secret hash", ex); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment