如何在Cognito联合身份验证中访问用户的电子邮件地址?

15
我正在尝试设置一个基本网站(在AWS上无服务器),允许访问者使用Google和/或Facebook登录。目前,我计划使用S3、具有联合身份认证的Cognito、API Gateway、Lambda(NodeJS)以及DynamoDB。客户端应用程序将使用Angular。
我已经实现了与Google和Facebook的社交登录,并且当前在用户第一次登录时插入了一个“用户”表中的行,其中包括cognitoId、姓名、个人图片URL等信息。
我还认为,以电子邮件地址作为键存储用户的信息是一个好的设计,而不是像cognitoId这样的东西,这样用户可以使用不同的提供商进行登录并查看相同的数据。因此,我需要知道经过身份验证的用户的电子邮件地址,但我认为它应该来自Cognito,而不是直接来自用户(因为客户端应用程序不应该受信任)。
我相信Cognito正在存储用户的电子邮件地址,因为我已经在用户池中启用了该字段作为必填字段。
我遇到的问题是找不到有关如何从Cognito获取用户电子邮件地址的任何信息。

我最接近的一篇文章是这篇,但我找不到访问令牌的位置:如何使用cognito身份ID获取用户属性(用户名、电子邮件等)

这篇文章表明我可以使用GetUser,但我不知道AccessToken来自哪里:使用AWS cognito identity创建用户

如果我确实需要使用GetUser和AccessToken,它来自哪里,我该如何生成它? 它来自客户端,还是我可以在Lambda中使用AWS.config.credentials获取它?

我一直在努力弄清楚这个问题,感觉好像我错过了什么非常简单的东西!


如果您没有从用户那里获取电子邮件并将其推送到Cognito,则无法获得它。您需要从用户那里获取所需的任何内容并存储在Cognito中。 - Sinan Gedik
有人有答案吗?我也面临着同样的挑战。 - Joseph Bolade Caxton-Idowu
有人有答案吗?我也面临同样的挑战。问题在于使用联合用户身份而不是Cognito用户池时。由于联合身份正在与提供商(如Facebook)通信,您会期望用户电子邮件地址将存储在AWS某个地方。或者它应该是可配置的,但到目前为止在任何文档中都找不到这个信息... - Joseph Bolade Caxton-Idowu
3个回答

5
首先,进入Cognito Identity提供程序(在Cognito控制台中),并确保您的提供程序“授权范围”适合使用。例如,如果您点击了Google提供程序,则授权范围可能为“profile email openid”。授权范围将因提供程序而异,但无论使用什么范围,它必须提供访问用户电子邮件的权限。
当您的用户使用外部身份提供程序(比如Facebook)登录时,Cognito会与Facebook协商,然后调用您在Cognito控制台的“应用客户端设置”部分设置的回调URL。该回调包含一个称为“code”的参数 - 该参数在由Cognito进行的回调的URL中设置。该代码是OAuth令牌。
现在您在客户端中有了OAuth令牌,就需要将其POST到AWS Token Endpoint。令牌终结点返回响应中的三个新令牌:JWT ID令牌、JWT访问令牌和刷新令牌。从终结点响应中获取“id_token”属性。将其解析为json字符串,并取出其中的“email”元素。现在您应该有用户的电子邮件地址。
这是我的Java工作示例。这是一个由Cognito Callback调用的servlet。
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.nimbusds.jwt.SignedJWT;
import net.minidev.json.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;

public class CognitoLandingServlet extends HttpServlet {

    static final Logger LOG = LoggerFactory.getLogger(CognitoLandingServlet.class);
    private static final long serialVersionUID = 1L;

    public CognitoLandingServlet() {
        super();
    }

    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {

        // Get the OpenID Connect (OAuth2) token passed back from the hosted Cognito
        // Login Page
        final String code = request.getParameter("code");
        LOG.debug(String.format("Cognito OAuth2 code received from Cognito: %s.", code));

        if (code != null) {
            // do nothing, we have a code as expected
        } else {
            LOG.debug(String.format(
                    "Landing page requested without a Cognito code, the request probably didn't come from Cognito"));
            // we dont have a token so redirect the user to the application sign in
            // page
            request.getRequestDispatcher("/signin").forward(request, response);
        }
        // Exchange the OIDC token for Cognito Access and ID JWT tokens using AWS
        // Token
        // Endpoint
        // There does not appear to be a Java SDK to handle this :(
        final String cognitoClientId = System.getProperty("CognitoClientId");
        final String redirectUri = System.getProperty("CognitoCallBackUrl");
        final String awsTokenEndpoint = System.getProperty("AwsTokenEndpoint");
        final String jwt = swapOauthForJWT(cognitoClientId, code, redirectUri, awsTokenEndpoint);
        // Complete the login using the JWT token string
        loginWithJWT(jwt, request, response);
    }

    @Override
    protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {

    }

    private void loginWithJWT(final String jwtString, final HttpServletRequest request,
                              final HttpServletResponse response) {

        final JSONParser parser = new JSONParser();
        SignedJWT signedIdJWT;

        try {
            // Take the id token
            final JSONObject json = (JSONObject) parser.parse(jwtString);
            final String idToken = (String) json.get("id_token");

            // Access token is not currently used
            // String accessToken = (String) json.get("access_token");

            // Process the id token
            signedIdJWT = SignedJWT.parse(idToken);
            final String userId = signedIdJWT.getJWTClaimsSet().getSubject();

            // Start NEW Session and start adding attributes
            final HttpSession session = request.getSession(true);
            session.setAttribute("userId", userId);

            final String cognitoUsername = (String) signedIdJWT.getJWTClaimsSet()
                    .getClaim("cognito:username");
            if (cognitoUsername != null) {
                user.setUserName(cognitoUsername);
                session.setAttribute("username", cognitoUsername);
            }
            final String email = (String) signedIdJWT.getJWTClaimsSet().getClaim("email");
            if (email != null) {
                user.setEmail(email);
                session.setAttribute("email", email);
            }
            // Save the user to a database (code removed for stack overflow)

            //request.getRequestDispatcher("/dashboard").forward(request, response);
            response.sendRedirect("/dashboard");

            LOG.info(
                    String.format("A user with userid %s and email %s successfully signed in", userId, email));

        } catch (final java.text.ParseException e) {
            LOG.error(
                    String.format("The JWT token could not be parsed by JOSE library. %s", e.getMessage()));
        } catch (final ParseException e) {
            LOG.error(String.format("The JWT token could not be parsed by JSON simple library. %s",
                    e.getMessage()));
        } catch (final IOException e) {
            LOG.error(String.format("Failed to request webpage at the end of the login process - io. %s",
                    e.getMessage()));
        }
    }

    private String swapOauthForJWT(final String cognitoClientId, final String oauthCode,
                                   final String redirectUri, final String awsTokenEndpoint) throws IOException {

        // Build the URL to post to the AWS Token Endpoint
        final String urlParameters = String.format(
                "Content-Type=application/x-www-form-urlencoded&grant_type=authorization_code&client_id=%s&code=%s&redirect_uri=%s",
                cognitoClientId, oauthCode, redirectUri);
        LOG.debug(String.format("User is swapping OAuth token for a JWT using URL %s", urlParameters));
        final URL url = new URL(awsTokenEndpoint);
        final URLConnection conn = url.openConnection();
        conn.setDoOutput(true);
        final OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
        writer.write(urlParameters);
        writer.flush();
        // Read the data returned from the AWS Token Endpoint
        final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        final StringBuilder responseStrBuilder = new StringBuilder();
        String inputStr;
        while ((inputStr = reader.readLine()) != null) {
            responseStrBuilder.append(inputStr);
        }
        // Close the connection
        writer.close();
        reader.close();
        LOG.debug(String.format("Finished swapping OAuth token for a JWT"));

        return responseStrBuilder.toString();
    }
}

2

您还需要在用户池中添加属性映射。请检查是否忘记添加映射。您可以在用户池设置的“联合”下找到“属性映射”选项卡。


不确定官方的 Cognito 文档是否足够讲解这一步骤。我们忘记做这个步骤,浪费了几个小时来调试。 - TheBrockEllis

0
要获取电子邮件,您必须向身份提供者(Facebook、Google、用户池)请求它。
要从用户池中获取电子邮件,您需要执行以下操作:
cognitoUser.getUserAttributes(function(err, result) {
    if (err) {
        alert(err);
        return;
    }
    for (i = 0; i < result.length; i++) {
        console.log('attribute ' + result[i].getName() + ' has value ' + result[i].getValue());
    }
});

Cognito身份验证不会保存电子邮件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接