带有后端验证流程说明的Google登录

时间:2019-05-24 13:25:20

标签: spring-boot jwt single-page-application angular7 google-signin

我花了数小时浏览各种教程和文章,并最终屈服于要求。

我想对Angular 7应用程序的所有用户强制使用Google登录身份验证。但是,一旦Google身份验证完成,我想首先检查该用户是否存在于我的后端数据库(PostgreSQL)中。如果他们这样做,那么我想为两个目的发布JWT:

  1. 确保以后仅对授权用户满足对Spring Boot REST服务的调用。
  2. 使用可识别令牌的AuthGuard保护我的Angular路线。

到目前为止,我已经能够从gapi auth2 auth响应中检索id_token并将其转发到我的Spring Boot POST映射,但是我一直在努力确定我所使用的OAuth 2.0 / OpenId的流向/授权旨在寻找合适的Spring Boot文档/教程时,这样做会给生活带来困难。

有人能弄清楚我应该瞄准哪个流程/拨款以及我目前的方向是否有效?

先谢谢了。 里斯

2 个答案:

答案 0 :(得分:1)

我建议您结合Google登录ID提供商实施“无状态”身份验证系统。

  

“使用JWT作为授权的承载者,您可以通过简单地检查有效载荷中的到期是否尚未到期以及签名是否有效来无状态地验证用户是否已通过身份验证。”   -乔纳坦·尼尔森(Jonatan Nilsson)

关于该主题的一些很好的资源:

总体思路是:

  • frontend检索Google登录身份验证JWT令牌。
  • 前端通过每个HTTP请求(带有授权标头)发送JWT令牌
  • 后端为每个请求检索JWT,验证其签名并获取有效载荷属性(电子邮件,id…)
  • 然后,后端在用户数据库中检查“电子邮件”或“ id”以允许或不允许请求。

后端是无状态的,并且易于实现。 这种设计倾向于在云平台上成为一种好的做法,例如,Google Cloud在其新产品中大量使用了这种设计:Cloud Run

每个步骤的一些详细信息:

1)前端检索Google登录身份验证JWT令牌。

为此,您可以直接使用Google登录库,也可以使用ng-gapi在Angular中管理Google登录。

2)每个对后端的http调用都有一个带有JWT令牌的授权标头(id_token)。

您可以为此使用HttpInterceptor。

headers: {
  Authorization: Bearer ___JWT ID TOKEN___
}

请参阅MichaelKarén的Top 10 ways to use Interceptors in Angular

请注意,不要将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 Sign-in, Authenticate with a backend server

答案 1 :(得分:0)

我还在研究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}";
}