Вход в Google с уточнением потока проверки бэкэнда - PullRequest
9 голосов
/ 24 мая 2019

Я провел часы, просматривая различные учебные пособия и статьи, и, наконец, отвечаю на вопрос.

Я хочу навязать использование аутентификации входа в Google для всех пользователей моего приложения Angular 7.Однако после завершения аутентификации Google я хочу сначала проверить, существует ли пользователь в моей внутренней БД (PostgreSQL).Если они это сделают, я хочу выпустить JWT для двух целей:

  1. Убедитесь, что будущие вызовы моих служб Spring Boot REST будут выполняться только для авторизованных пользователей.
  2. Защита моего AngularМаршруты с использованием AuthGuard, который знает о токене.

До сих пор я был в состоянии извлечь id_token из ответа auth2 gapi auth2 и переслать его в мое сопоставление Spring Boot POST, но я 'Я изо всех сил пытаюсь точно определить, какие потоки / гранты OAuth 2.0 / OpenId, к которым я стремлюсь, усложняет жизнь при поиске соответствующей документации / учебников Spring Boot.

Кто-нибудь может уточнить, какой поток / грант мне следуетстремиться к тому, действительно ли мое текущее направление?

Ответы [ 2 ]

1 голос
/ 01 июля 2019

Я предлагаю вам внедрить систему аутентификации без сохранения состояния в сочетании с провайдером идентификатора входа в систему Google.

«Используя JWT в качестве канала для авторизации, вы можете без проверки состояния проверить, является ли пользовательаутентифицируется, просто проверяя, не истек ли срок действия в полезной нагрузке и действительна ли подпись ». - Джонатан Нильссон

Некоторые полезные ресурсы по теме:

Общая идея такова:

  • веб-интерфейс получает аутентификацию для входа в GoogleТокен JWT.
  • веб-интерфейс отправляет токен JWT с каждым HTTP-запросом (с заголовком авторизации)
  • бэкэнд извлекает JWT для каждого запроса, проверяет его подпись и получает атрибуты полезной нагрузки (электронная почта, идентификатор ...)
  • затем бэкэнд проверяет 'email' или 'id' в базе данных пользователей, чтобы разрешить или не запросить.

Бэкэнд не имеет состояния и прост в реализации.Этот дизайн становится хорошей практикой в ​​облачной платформе, и, например, Google Cloud часто использует это в своих новых продуктах: Cloud Run

Некоторые подробности на каждом этапе:

1) веб-интерфейс получает токен аутентификации JWT для входа в Google.

Для этого вы можете использовать библиотеку входа в Google напрямую или использовать ng-gapi для управления входом в Google в Angular.

2) Каждый http-вызов к бэкэнду имеет заголовок авторизации с токеном JWT (id_token), полученный из GoogleВход в систему.

Для этого вы можете использовать HttpInterceptor.

headers: {
  Authorization: Bearer ___JWT ID TOKEN___
}

См. 10 лучших способов использования перехватчиков в Angular от Michael Karén.

Обратите внимание, чтобы не хранить Google JWT Id_token в переменной.Его можно обновить, если срок его действия истек (автоматически выполняется при входе в Google), поэтому при каждом использовании внутри HttpInterceptor вы должны брать новую версию.

3) Внедрить фильтр в Spring Boot

Для каждого запроса этот фильтр безопасности будет извлекать JWT ID TOKEN и проверять его с помощью библиотеки Google.

NetHttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new GsonFactory();

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
  .setAudience(Collections.singletonList(clientId))
  .build();

GoogleIdToken idToken = GoogleIdToken.parse(verifier.getJsonFactory(), token);
boolean tokenIsValid = (idToken != null) && verifier.verify(idToken);

if (tokenIsValid) {
  GoogleIdToken.Payload payload = idToken.getPayload();

  // Get profile information from payload
  payload.getEmail())...
...

Но будьте осторожны, чтобы не создавать GoogleIdTokenVerifier для каждого запроса,используйте шаблон factory.Этот класс будет извлекать сертификаты и автоматически их кэшировать, чтобы избежать бесполезного запроса к серверам Google.

Некоторые ресурсы: Вход в Google, аутентификация на бэкэнд-сервере

0 голосов
/ 30 мая 2019

Я также работаю над Google Auth / OAuth 2.0 / API управления учетными данными и могу сочувствовать разочарованию от примеров и кроличьих дыр в Интернете.

Не уверен, что на ваш вопрос отвечают следующие вопросы, но я делаю это после завершения API управления учетными данными и внешнего интерфейса Google: -

public String ValidateToken(string idToken, string accessToken)
{
    bool isValid = false;
    string errorMessage = "Illegal Access Token: ";

    if (accessToken != null)
    {
        var tokeninfoRequest = new Oauth2Service().Tokeninfo();
        tokeninfoRequest.AccessToken = accessToken;
        Tokeninfo tokeninfo = null;
        try
        {
            tokeninfo = tokeninfoRequest.Execute();
            if (tokeninfo.IssuedTo != CLIENT_ID)
            {
                errorMessage += "Imposter";
            }
        }
        catch (Exception e)
        {
            errorMessage += e.Message;
        }
        isValid = true;
    }

    if (!isValid) abortRequest(401, errorMessage);

    errorMessage = "Invalid ID Token";
    string gplus_id = "";
    JwtSecurityToken JWToken = new JwtSecurityToken(idToken);

    Byte[][] certBytes = getCertBytes(GOOGLE_CERTS);
    Dictionary<String, X509Certificate2> certificates = new Dictionary<String, X509Certificate2>();
    for (int i = 0; i < certBytes.Length; i++)
    {
        X509Certificate2 certificate = new X509Certificate2(certBytes[i]);
        certificates.Add(certificate.Thumbprint, certificate);
    }

    TokenValidationParameters JWTparams = new TokenValidationParameters()
    {
        ValidateActor = false,
        ValidateAudience = true, 
        ValidAudience = CLIENT_ID,
        ValidateIssuer = true, 
        ValidIssuers = VALID_ISSUERS,
        ValidateIssuerSigningKey = true,
        RequireSignedTokens = true,
        IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)),
        IssuerSigningKeyResolver = (tokenString, securityToken, kid, parameters) =>
        {
            return certificates
           .Where(x => x.Key.ToUpper() == kid.ToUpper())
           .Select(x => new X509SecurityKey(x.Value));
        },
        ValidateLifetime = true,
        RequireExpirationTime = true,
        ClockSkew = TimeSpan.FromHours(13)
    };

    Claim[] claims = JWToken.Claims.ToArray<Claim>();
    for (int i = 0; i < claims.Length; i++)
    {
        if (claims[i].Type.Equals("sub"))
        {
            gplus_id = claims[i].Value;
            break;
        }
    }

    if (gplus_id == null) abortRequest(401, "Invalid Google id");

    SecurityToken validatedToken;
    ClaimsPrincipal cp;
    JwtSecurityTokenHandler JWThndlr = new JwtSecurityTokenHandler();
    try
    {
        cp = JWThndlr.ValidateToken(idToken, JWTparams, out validatedToken);
    }
    catch
    {
        cp = null;
    }

    if (cp == null) abortRequest(401, "Invalid ID Token");

    return "{\"success\": true}";
}
...