通过 Cognito Identity Pool 验证的用户的 MQTT 连接

MQTT connection for users authenticated via Cognito Identity Pool

很快:我有一个联合身份池,它具有用于未经身份验证和经过身份验证的访问的角色。我对未经身份验证的访问没有任何问题。但是当涉及到经过身份验证的访问时,用户登录就好了,但我对经过身份验证的用户的角色并没有得到实际应用。


我有一个 s3 存储桶,其中包含简单的 index.html 和 index.js 文件,这些文件通过 MQTT 进行通信。

经过身份验证和未经身份验证的用户的策略现在看起来完全相同,并且相当宽松(当然这不是用于生产的方式,但我只是试图让它以任何方式工作到目前为止).因此,这两项政策如下所示:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

而在index.js中我可以以未认证用户的身份建立MQTT连接,如下:

var region = 'eu-west-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: 'my-identity-pool-id',
});
AWS.config.credentials.clearCachedId();
AWS.config.credentials.get(function(err) {
  console.log('accessKeyId:', AWS.config.credentials.accessKeyId);
  if(err) {
    console.log(err);
    return;
  }
  var requestUrl = SigV4Utils.getSignedUrl(
    'wss',
    'data.iot.' + region + '.amazonaws.com',
    '/mqtt',
    'iotdevicegateway',
    region,
    AWS.config.credentials.accessKeyId,
    AWS.config.credentials.secretAccessKey,
    AWS.config.credentials.sessionToken
  );

  initClient(requestUrl);
});

initClient()只是通过Paho:

建立MQTT连接
function initClient(requestUrl) {
  var clientId = String(Math.random()).replace('.', '');
  var rpcId = "smart_heater_" + String(Math.random()).replace('.', '');
  var client = new Paho.MQTT.Client(requestUrl, clientId);
  var connectOptions = {
    onSuccess: function () {
      console.log('connected');
      // Now I can call client.subscribe(...) or client.send(...)
    },
    useSSL: true,
    timeout: 3,
    mqttVersion: 4,
    onFailure: function (err) {
      console.error('connect failed', err);
    }
  };
  client.connect(connectOptions);

  client.onMessageArrived = function (message) {
    console.log("msg arrived: " +  message);
  };
}

它工作得很好:connected 被打印到控制台,我实际上可以 send/subscribe。

现在,我正在尝试对经过身份验证的用户执行相同的操作。为此,我添加了 Cognito 用户池,在那里创建了一个用户,创建了一个 "app",将身份验证提供程序 "Cognito" 添加到我的身份池(具有适当的用户池 ID 和应用程序客户端 ID),以及身份验证本身工作正常:用户登录。代码如下所示:

var region = 'eu-west-1';
var poolData = {
  UserPoolId: 'eu-west-1_XXXXXXXXX',
  ClientId: 'ZZZZZZZZZZZZZZZZZZZZZZZZZ',
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);

var authenticationData = {
  Username: 'myusername',
  Password: 'mypassword',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
  Username: 'myusername',
  Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
  onSuccess: function (result) {
    console.log('result:', result);
    console.log('access token: ' + result.getAccessToken().getJwtToken());

    var myUserPoolId = 'eu-west-1_XXXXXXXXX';
    console.log('You are now logged in.');

    // Add the User's Id Token to the Cognito credentials login map.
    var logins = {};
    logins['cognito-idp.' + region + '.amazonaws.com/' + myUserPoolId] = result.getIdToken().getJwtToken();

    AWS.config.credentials.params.Logins = logins;
    // finally, expire the credentials so we refresh on the next request
    AWS.config.credentials.expired = true;

    //call refresh method in order to authenticate user and get new temp credentials
    AWS.config.credentials.refresh((error) => {
      if (error) {
        console.error(error);
      } else {
        console.log('Successfully logged!');
        console.log('accessKeyId:', AWS.config.credentials.accessKeyId);

        var requestUrl = SigV4Utils.getSignedUrl(
          'wss',
          'data.iot.' + region + '.amazonaws.com',
          '/mqtt',
          'iotdevicegateway',
          region,
          AWS.config.credentials.accessKeyId,
          AWS.config.credentials.secretAccessKey,
          AWS.config.credentials.sessionToken
        );

        initClient(requestUrl);
      }
    });
  },

  onFailure: function (err) {
    alert(err);
  },

  newPasswordRequired: function(userAttributes, requiredAttributes) {
    // User was signed up by an admin and must provide new 
    // password and required attributes, if any, to complete 
    // authentication.

    // the api doesn't accept this field back
    delete userAttributes.email_verified;

    var newPassword = prompt('Enter new password ', '');
    // Get these details and call 
    cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
  },
});

登录部分并非易事,但现在可以正常工作:在控制台中,我看到:

You are now logged in.
Successfully logged!
accessKeyId: ASIAIRV4HOMOH6DXFTWA

然后,在连接时,出现以下错误:

connect failed: {invocationContext: undefined, errorCode: 8, errorMessage: "AMQJS0008I Socket closed."}

我假设这是因为实际应用于经过身份验证的用户的角色不允许该操作 iot:Connect;我相信是这样,因为我可以为未经身份验证的用户重现完全相同的错误,如果我在未经身份验证的用户的策略中添加以下内容:

{
    "Action": [
        "iot:Connect"
    ],
    "Resource": "*",
    "Effect": "Deny"
},

然后未经身份验证的用户在尝试连接时将收到相同的错误 AMQJS0008I Socket closed

因此,看起来我针对经过身份验证的用户的策略实际上并未应用于经过身份验证的用户。

在写这道题之前我做了很多实验。目前,在我的身份池设置中,在 "Authenticated role selection" 中我只有 "Use default role",它确实应该选择我的身份验证角色,即:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

但我也尝试 select "Choose role with rules" 并编写一个规则,根据一些匹配的子句设置我的角色。

我还尝试在我的用户池中创建一个组,在那里使用相同的角色,将我的用户添加到该组,并在身份池设置中设置 "Choose role from token"。

没有任何帮助。对于经过身份验证的用户,我不断收到此 errorMessage: "AMQJS0008I Socket closed." 消息,而对于未经身份验证的用户,一切正常,即使策略相同。

感谢任何帮助。

问题是,对于经过身份验证的 Cognito 用户,将 IAM 策略附加到身份池是不够的:除此之外,还必须附加 IoT 策略(不是 IAM政策)到每个身份(基本上,每个用户),像这样:

$ aws iot attach-principal-policy \
    --policy-name Some-Policy \
    --principal us-east-1:0390875e-98ef-420d-a52d-f4188ce3cf06

也检查这个线程https://forums.aws.amazon.com/thread.jspa?messageID=726121