Iam trying to authenticate a Java app with Cognito.
I have used for python the warrant library that worked very good. But i want to do the same in java now.
My Python function i used for authentication with the warrant library
def SRPauthentication(organizationAdmin,
password,
pool_id,
client_id,
client):
aws = AWSSRP(username=organizationAdmin,
password=password,
pool_id=pool_id,
client_id=client_id,
client=client)
tokens = aws.authenticate_user()
authorization_token= tokens['AuthenticationResult']['IdToken']
return authorization_token
with this i could easily acces some secured APIs.
Now i want to do the same with Java but i have problems.
This is my solution so far is this method:
public static void GetCreds()
{
AWSCognitoIdentityProvider identityProvider = AWSCognitoIdentityProviderClientBuilder.defaultClient();
AdminInitiateAuthRequest adminInitiateAuthRequest = new AdminInitiateAuthRequest().
withAuthFlow(AuthFlowType.USER_SRP_AUTH).
withClientId("234234234234").withUserPoolId("eu-central-1_sdfsdfdsf")
.addAuthParametersEntry("USERNAME", "UserK").
addAuthParametersEntry("PASSWORD","#######);
adminInitiateAuthRequest.getAuthFlow();
AdminInitiateAuthResult adminInitiateAuth = identityProvider.adminInitiateAuth(adminInitiateAuthRequest);
System.out.println(adminInitiateAuth.getAuthenticationResult().getIdToken());
}
When i run this i get an Exception:
Exception in thread "main" `com.amazonaws.services.cognitoidp.model.AWSCognitoIdentityProviderException: User: arn:aws:iam::XXXXXXXXXXXXXXXXX:user/khan is not authorized to perform: cognito-idp:AdminInitiateAuth on resource: arn:aws:cognito-idp:eu-central-1:XXXXXXXX:userpool/eu-central-1_XXXXXXX with an explicit deny (Service: AWSCognitoIdentityProvider; Status Code: 400; Error Code: AccessDeniedException; Request ID: 21be0b8e-adec-11e8-ad45-234234234)`
It says iam not authorized to perform this kind of instruction. So i guess iam doing something generally wrong. Because its working with my python code and in Java it recognizes my username from the credentials. The Cognito call should actually be independent from my aws credentials/useraccount right?
How to authenticate with Cognito using Java to get an Token to access secured aws services?
EDIT:
AWSCognitoIdentityProvider identityProvider = AWSCognitoIdentityProviderClientBuilder.standard()
.build();
InitiateAuthRequest adminInitiateAuthRequest = new InitiateAuthRequest()
.withAuthFlow(AuthFlowType.USER_SRP_AUTH)
.withClientId("XXXXXXXXXXXXXXXXX")
.addAuthParametersEntry("USERNAME", "user").
addAuthParametersEntry("PASSWORD","za$Lwn")
.addAuthParametersEntry("SRP_A",new AuthenticationHelper("eu-central-1XXXXXXXXX").getA().toString(16));
adminInitiateAuthRequest.getAuthFlow();
InitiateAuthResult adminInitiateAuth = identityProvider.initiateAuth(adminInitiateAuthRequest);
System.out.println(adminInitiateAuth);
I changed the AdminInitateAuthRequest to InitateAuthRequest. After that i had the Error missing SRP_A parameter that i somehow fixed with a similiar question here
And now i recive this :
{ChallengeName: PASSWORD_VERIFIER,ChallengeParameters: {SALT=877734234324234ed68300f39bc5b, SECRET_BLOCK=lrkwejrlewrjlewkjrewlrkjwerlewkjrewlrkjewrlkewjrlewkrjZ+Q==, USER_ID_FOR_SRP=user, USERNAME=user, SRP_B=43ecc1lwkerjwelrkjewlrjewrlkewjrpoipweoriwe9r873jr34h9r834hr3455f7d079d71e5012f1623ed54dd10b832792dafa3438cca3f59c0f462cbaee255d5b7c2werwerwerkjweorkjwerwerewrf5020e4f8b5452f3b89caef4a797456743602b80b5259261f90e52374adc06b456521a9026cce9c1cbe8b9ffd6040e8c1589d35546861422110ac7e38c1c93389b802a03e3e2e4a50e75d088275195f836f66e25f1a431dd56bb2},}
I have shorten the result with all the keys, but what to do next ?
Finally i could solve it with this code class.
There are multiple challenges involved in SRP authentication. The InitiateAuthRequest is one first request that is necessary.
This similiar question helped me :
stackoverflow
stackoverfow
String PerformSRPAuthentication(String username, String password) {
String authresult = null;
InitiateAuthRequest initiateAuthRequest = initiateUserSrpAuthRequest(username);
try {
AnonymousAWSCredentials awsCreds = new AnonymousAWSCredentials();
AWSCognitoIdentityProvider cognitoIdentityProvider = AWSCognitoIdentityProviderClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.withRegion(Regions.fromName(this.region))
.build();
InitiateAuthResult initiateAuthResult = cognitoIdentityProvider.initiateAuth(initiateAuthRequest);
if (ChallengeNameType.PASSWORD_VERIFIER.toString().equals(initiateAuthResult.getChallengeName())) {
RespondToAuthChallengeRequest challengeRequest = userSrpAuthRequest(initiateAuthResult, password);
RespondToAuthChallengeResult result = cognitoIdentityProvider.respondToAuthChallenge(challengeRequest);
//System.out.println(result);
System.out.println(CognitoJWTParser.getPayload(result.getAuthenticationResult().getIdToken()));
authresult = result.getAuthenticationResult().getIdToken();
}
} catch (final Exception ex) {
System.out.println("Exception" + ex);
}
return authresult;
}
private InitiateAuthRequest initiateUserSrpAuthRequest(String username) {
InitiateAuthRequest initiateAuthRequest = new InitiateAuthRequest();
initiateAuthRequest.setAuthFlow(AuthFlowType.USER_SRP_AUTH);
initiateAuthRequest.setClientId(this.clientId);
//Only to be used if the pool contains the secret key.
//initiateAuthRequest.addAuthParametersEntry("SECRET_HASH", this.calculateSecretHash(this.clientId,this.secretKey,username));
initiateAuthRequest.addAuthParametersEntry("USERNAME", username);
initiateAuthRequest.addAuthParametersEntry("SRP_A", this.getA().toString(16));
return initiateAuthRequest;
}
Related
I'm trying to use the Azure Workload Identity MSAL Java Sample, and I'm trying to figure out if the built-in token cache that comes with MSAL4J is actually usable with Azure Workload Identity (Client Assertions), as my understanding is that every time you request a new token, you need to read the AZURE_FEDERATED_TOKEN_FILE again (See // 1). I've looked through the MSAL4J code and to me it looks like you'd need to throw away the ConfidentialClientApplication (see // 2) and create a brand new one to load in a new federated token file, because the clientAssertion ends up baked into the client. So then I'd need to do my own checks to figure out if I need if I need to recreate the client, basically defeating the purpose of the built-in client.
Are my assumptions correct? Or is there some way to hook into the token refresh process and reload the clientAssertion?
Maybe MSAL4J needs integrated token cache support for Azure Workload Identity that handles the reloading of the client assertion on renewal?
Here is the sample code included for context.
public class CustomTokenCredential implements TokenCredential {
public Mono<AccessToken> getToken(TokenRequestContext request) {
Map<String, String> env = System.getenv();
String clientAssertion;
try {
clientAssertion = new String(Files.readAllBytes(Paths.get(env.get("AZURE_FEDERATED_TOKEN_FILE"))),
StandardCharsets.UTF_8); // 1
} catch (IOException e) {
throw new RuntimeException(e);
}
IClientCredential credential = ClientCredentialFactory.createFromClientAssertion(clientAssertion);
String authority = env.get("AZURE_AUTHORITY_HOST") + env.get("AZURE_TENANT_ID");
try {
ConfidentialClientApplication app = ConfidentialClientApplication
.builder(env.get("AZURE_CLIENT_ID"), credential).authority(authority).build(); // 2
Set<String> scopes = new HashSet<>();
for (String scope : request.getScopes())
scopes.add(scope);
ClientCredentialParameters parameters = ClientCredentialParameters.builder(scopes).build();
IAuthenticationResult result = app.acquireToken(parameters).join();
return Mono.just(
new AccessToken(result.accessToken(), result.expiresOnDate().toInstant().atOffset(ZoneOffset.UTC)));
} catch (Exception e) {
System.out.printf("Error creating client application: %s", e.getMessage());
System.exit(1);
}
return Mono.empty();
}
}
I have code where i'm connecting to TFS using java tfs sdk like below,
*
*Credentials credentials = new UsernamePasswordCredentials( "xxxxxx", "XXXXX"); getConnection(serverURI, credentials, maxDate, buildNo);
}
private static void getConnection(URI serverURI, final Credentials credentials, String maxDate, String buildNo)
{
TFSTeamProjectCollection tpc = null;
try{
tpc = new TFSTeamProjectCollection(serverURI, credentials);
tpc.authenticate();
}catch(Exception e){
System.out.println(e.getMessage());
}*
But its stop authenticating using username and password, what the alternative authentication type like PAT - i need code to authenticate it, please help on this.
I tested with username and password as credentials, and I can connect to TFS normally on my side.
Here is a ticket you can refer to .
I am doing kerberos delegation. I noticed that GSSUtil.createSubject(context.getSrcName(), clientCred) returns a Subject without having credentials in it.
Prior to that i've done GSSCredential clientCred = context.getDelegCred(); which returns the credentials.
Edit: When I hit my service from one machine in same domain, it works, while if accessed from other machine in same domain, it doesn't.
Confused what additional settings are needed on AD ?
Any help is highly appreciated.
Following is my code:
public class KerberosTest {
public Subject loginImpl(byte[] kerberosTicket, String propertiesFileName) throws Exception {
System.setProperty("sun.security.krb5.debug", "true");
// // no effect // System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
final Krb5LoginModule krb5LoginModule = new Krb5LoginModule();
Subject serviceUserSubject = new Subject();
final Map<String,String> optionMap = new HashMap<String,String>();
HashMap<String, String> shared = new HashMap<>();
optionMap.put("keyTab", "C:\\kerberos_files\\sapuser.keytab");
optionMap.put("principal", "HTTP/SAPTEST#EQSECTEST.LOCAL"); // default realm
// optionMap.put("principal", "kerberosuser"); // default realm
optionMap.put("useFirstPass", "true");
optionMap.put("doNotPrompt", "true");
optionMap.put("refreshKrb5Config", "true");
optionMap.put("useTicketCache", "false");
optionMap.put("renewTGT", "false");
optionMap.put("useKeyTab", "true");
optionMap.put("storeKey", "true");
optionMap.put("isInitiator", "true");
optionMap.put("useSubjectCredsOnly", "false");
optionMap.put("debug", "true"); // switch on debug of the Java implementation
krb5LoginModule.initialize(serviceUserSubject, null, shared, optionMap);
// login using details mentioned inside keytab
boolean loginOk = krb5LoginModule.login();
System.out.println("Login success: " + loginOk);
// This API adds Kerberos Credentials to the the Subject's private credentials set
boolean commitOk = krb5LoginModule.commit();
}
System.out.println("Principal from subject: " + serviceUserSubject.getPrincipals()); // this must display name of user to which the keytab corresponds to
Subject clientSubject = getClientContext(serviceUserSubject, kerberosTicket);
System.out.println("Client Subject-> " + clientSubject);
System.out.println("Client principal-> "+clientSubject.getPrincipals().toArray()[0]);
return clientSubject;
}
// Completes the security context initialisation and returns the client name.
private Subject getClientContext(Subject subject, final byte[] kerberosTicket) throws PrivilegedActionException {
Subject clientSubject = Subject.doAs(subject, new KerberosValidateAction(kerberosTicket));
return clientSubject;
}
private class KerberosValidateAction implements PrivilegedExceptionAction<Subject> {
byte[] kerberosTicket;
public KerberosValidateAction(byte[] kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}
#Override
public Subject run() throws Exception {
GSSManager gssManager = GSSManager.getInstance();
GSSContext context = gssManager.createContext((GSSCredential) null);
Oid kerberosOid = new Oid("1.2.840.113554.1.2.2");
// context.requestCredDeleg(true); // needed when we are demanding ticket from KDC. In our scenario, we are getting ticket from browser(client)
// Called by the context acceptor upon receiving a token from the peer. This is our context acceptor
// This method may return an output token which the application will need to send to the peer for further processing by its initSecContext call.
// We will only accept the incoming token from Peer (browser) and fwd it to third party system
while (!context.isEstablished()) {
byte[] nextToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
}
boolean established = context.isEstablished();
String user = context.getSrcName().toString();
String serviceAccnt = context.getTargName().toString();
//check if the credentials can be delegated
if (!context.getCredDelegState()) {
System.out.println("credentials can not be delegated!");
return null;
}
//get the delegated credentials from the calling peer...
GSSCredential clientCred = context.getDelegCred();
//Create a Subject out of the delegated credentials.
//With this Subject the application server can impersonate the client that sent the request.
Subject clientSubject = GSSUtil.createSubject(context.getSrcName(), clientCred);
return clientSubject; // ***this contains only principal name and not credentials !!
}
}
I figured out one important point, which I didn't find anywhere documented. -
While configuring the kerberos service user in AD, "Trust this user for delegation (Kerberos Only)" will come in effect only for new TGTs which are created after this change is saved.
For example, if the user is logged in on client machine A, say at 10:00 AM.
Administrator enables the delegation to kerberos service user at 10:30 AM.
When the user hits an application URL from browser on machine A, the delegation will not work because his TGT is created before the delegation is turned on (at 10:00 AM).
If user logs out from the machine A and relogins, a new TGT will be generated. Delegation works perfectly for this user now.
With the code I'm using VMWare created an object factory to use with their Rest API. One of them is a clientconfig class which allows me to connect to the Rest API Service.
I'm trying to prompt the user to input the password versus storing it in my java client. I suspect I have to remove the basicauth object and replace it, however I'm hoping there might be an easier way.
Here is my code:
public static void main(String[] args) {
try {
Client client = ClientConfig
.builder()
.serverUrl("https://vrops-ip-address/suite-api")
.basicAuth("admin", "password")
.verify("false")
.useJson()
.build()
.newClient();
// retrieve list of all adapter instances
AdapterInstancesInfoDto adapterInstances = client.adapterInstancesClient().list();
// get actual set of adapters
Set<AdapterInstanceInfoDto> adapters = adapterInstances.getAdapterInstancesInfoDto();
// iterate the set
for (AdapterInstanceInfoDto adapter : adapters) {
// print to console
System.out.println(ReflectionToStringBuilder.reflectionToString(adapter, ToStringStyle.MULTI_LINE_STYLE));
}
} catch (Exception e) {
e.printStackTrace();
}
}
and the basicauth object is as follows:
public Builder basicAuth(String username, String password) {
}
The builder class is read only so I can't change basicauth. Any reference or advice would be helpful here.
Just ask for the password before you create the connection.
System.err.print("Username: ");
String user = System.console().readLine();
System.err.print("Password: ");
String password = System.console().readPassword();
try {
Client client = ClientConfig
.builder()
.serverUrl("https://vrops-ip-address/suite-api")
.basicAuth(username, password)
.verify("false")
.useJson()
.build()
.newClient();
.
.
.
I'm using amazon SDK 1.5.6 and trying to initialize the connection using a proxy server.
The initialization succeed, but when I try to use the AmazonEC2Client it fails with the following
error:
Caught Exception: Status Code: 407, AWS Service: AmazonEC2, AWS
Request ID: null, AWS Error Code: 407 Unauthorized, AWS Error Message:
Unable to unmarshall error response (Premature end of file.) Reponse
Status Code: 407 Error Code: 407 Unauthorized Request ID: null
Initialization code:
protected AmazonEC2 initAmazonSDKClient(String endpoint) {
AWSCredentials awsCredentials =
new BasicAWSCredentials(_account.getAccessKey(), _account.getSecretKey());
ClientConfiguration config = getProxySettings();
AmazonEC2 ret = CloudServicesEC2Api.getAmazonEC2Client(awsCredentials, config);
ret.setEndpoint(endpoint);
_endPointToAmazonEC2Client.put(endpoint, ret);
return ret;
}
private ClientConfiguration getProxySettings() {
ClientConfiguration ret = new ClientConfiguration();
String host = _proxySettings.getServer();
if (!StringUtils.isNullOrEmpty(host )) {
ret.setProxyHost(host );
}
if (!StringUtils.isNullOrEmpty(_proxySettings.getPort())) {
ret.setProxyPort(Integer.valueOf(_proxySettings.getPort()));
}
String proxyUserName = _proxySettings.getUserName();
if (!StringUtils.isNullOrEmpty(proxyUserName)) {
ret.setProxyUsername(proxyUserName);
}
String proxyPassword = _proxySettings.getPassword();
if (!StringUtils.isNullOrEmpty(proxyPassword)) {
ret.setProxyPassword(proxyPassword);
}
ret.setProxyWorkstation(host );
ret.setProxyDomain(host );
return ret;
}
Code that uses the client and causes the error:
private List<InstanceStatus> getStatusChecks(AmazonEC2 ec2Client, String[] hostIds) {
DescribeInstanceStatusRequest describeInstanceRequest =
new DescribeInstanceStatusRequest().withInstanceIds(hostIds);
DescribeInstanceStatusResult describeInstanceResult =
ec2Client.describeInstanceStatus(describeInstanceRequest);
return describeInstanceResult.getInstanceStatuses();
}
I know the root cause is the NTLM authentication, I need to somehow configure the NTLM host & NTLM domain, in my code sample its the lines:
ret.setProxyWorkstation(host );
ret.setProxyDomain(host );
I have tried several versions of workstation & domain, i tried leaving it blank, no use...
Please advise!
Well, after some research it turns out that something is not right in the way Amazon SDK handles NTLM proxy requests. We've compared the proxy parameters passed to other libraries (e.g JClouds) where the connection succeeded and tried applying the same on Amazon SDK with no luck.
We have even compared the packets using wireshark, everything is the same. Something is just not right with this library...