使用 AspNet Core 2.0 进行 Google JWT 身份验证

Posted

技术标签:

【中文标题】使用 AspNet Core 2.0 进行 Google JWT 身份验证【英文标题】:Google JWT Authentication with AspNet Core 2.0 【发布时间】:2018-07-21 12:38:18 【问题描述】:

我正在尝试将 google 身份验证集成到我的 ASP.NET Core 2.0 Web api 中,但我不知道如何让它工作。

我的 Startup.cs ConfigureServices 中有这段代码:

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders();

services.AddAuthentication()
.AddGoogle(googleOptions => 
 
     googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
     googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
);

这个在Configure(IApplicationBuilder app, IHostingEnvironment env):

 app.UseAuthentication();

当我导航到Authorized 端点时,结果是302 Found,因为它可能正在重定向到某个登录端点(我从未创建过)。如果没有提供令牌,我如何防止重定向并且只让 API 期望一个令牌并返回一个 401

【问题讨论】:

我认为你需要指定架构名称,检查这个answer 即将修改问题,因为我似乎通过添加显然指定架构的 AddIdentity 取得了一些进展。 此 Google 提供商不处理 API 令牌。这就是 JwtBearer 的用途。或者您是否尝试将它与令牌并排使用? @Tratcher 我正在为移动客户端构建一个后端(反应原生)。我想我想要的只是让我的 API 能够处理从我的 react 本机前端提供的谷歌令牌。这个工作流程有意义吗?我使用的谷歌中间件是不是用了错误的方式来做到这一点?一般来说,我是 Auth 的新手。 google auth 处理程序是为交互式浏览器应用程序构建的,而不是 API 访问。如果来自 google 的令牌是 JWT,那么您可以使用 JwtBearer 处理程序处理它,该处理程序使用您要求的 401 身份验证流程。如果它不是 JWT,那么生活会变得更有趣。即使客户端向您发送令牌,您打算如何验证它?大多数令牌都是不透明的,您只能通过使用它们调用 Google API 来确认它们是有效的。您是否打算调用这些 API? 【参考方案1】:

为后代发布我的终极方法。

正如 Tratcher 所指出的,AddGoogle 中间件实际上并不适用于 JWT 身份验证流程。在做了更多的研究之后,我意识到我最终想要的是这里描述的内容: https://developers.google.com/identity/sign-in/web/backend-auth

所以我的下一个问题是

    我不能再依赖标准的 dotnet core Jwt auth 中间件,因为我需要将 google 令牌验证委托给 google 库 该页面上没有列为外部客户端库之一的 C# google 验证器。

经过更多的挖掘,我发现 JWT 验证支持被添加到 C# here 使用这个类和方法: Google.Apis.Auth.Task<GoogleJsonWebSignature.Payload> ValidateAsync(string jwt, GoogleJsonWebSignature.ValidationSettings validationSettings)

接下来我需要弄清楚如何替换内置的 JWT 验证。从这个 SO 问题我想出了一个方法: ASP.NET Core JWT Bearer Token Custom Validation

这是我的自定义 GoogleTokenValidator:

public class GoogleTokenValidator : ISecurityTokenValidator

    private readonly JwtSecurityTokenHandler _tokenHandler;

    public GoogleTokenValidator()
    
        _tokenHandler = new JwtSecurityTokenHandler();
    

    public bool CanValidateToken => true;

    public int MaximumTokenSizeInBytes  get; set;  = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

    public bool CanReadToken(string securityToken)
    
        return _tokenHandler.CanReadToken(securityToken);
    

    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    
        validatedToken = null;
        var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result; // here is where I delegate to Google to validate

        var claims = new List<Claim>
                
                    new Claim(ClaimTypes.NameIdentifier, payload.Name),
                    new Claim(ClaimTypes.Name, payload.Name),
                    new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
                    new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
                    new Claim(JwtRegisteredClaimNames.Email, payload.Email),
                    new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
                    new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
                ;

        try
        
            var principle = new ClaimsPrincipal();
            principle.AddIdentity(new ClaimsIdentity(claims, AuthenticationTypes.Password));
            return principle;
        
        catch (Exception e)
        
            Console.WriteLine(e);
            throw;

        
    

Startup.cs 中,我还需要清除默认的 JWT 验证,并添加我的自定义验证:

services.AddAuthentication(options =>
            
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            )
            .AddJwtBearer(o =>
                
                    o.SecurityTokenValidators.Clear();
                    o.SecurityTokenValidators.Add(new GoogleTokenValidator());
                

也许有更简单的方法,但这是我登陆的地方,它似乎工作正常!为了简单起见,我做了一些额外的工作,例如,检查我的用户数据库中是否已经有一个用户与谷歌提供的声明相匹配,所以如果上面的代码不能 100% 工作,我深表歉意我可能无意中删除了一些东西。

【讨论】:

这条线是否正确o.SecurityTokenValidators.Add(new JwtSecurityTokenHandler());?还是您的意思是添加您的 GoogleTokenValidator 课程? 您确认它是一个有效的令牌,但不是为您的网站设计的。您需要传递您在 google 中设置的受众(clientId)以提供额外的安全级别。您可以通过添加到new GoogleJsonWebSignature.ValidationSettings() 并设置属性Audience = "YourClientId" 来做到这一点。如果您不设置此选项,则据我所知,Google 验证会跳过受众验证。 @CodePB 我明白你在说什么。我刚看完解释 OAuth2 和 OpenID Connect 的 Pluralsight 课程,所以现在一切都变得更有意义了。感谢您的反馈!当我有时间更新我的代码时,我会更新答案。 @Mikeyg36 这不适用于 dotnet core 2.1。你可否确认?此外,无法找到对枚举 AuthenticationTypes.Password 的引用。 此答案所需的使用:使用 Google.Apis.Auth;使用系统;使用 System.Collections.Generic;使用 System.Security.Claims;使用 Microsoft.IdentityModel.Tokens;使用 System.IdentityModel.Tokens.Jwt;【参考方案2】:

我刚刚发布了NuGet package 来处理 Google OpenID Connect 令牌的验证。

该软件包依赖于来自 Microsoft.AspNetCore.Authentication.JwtBearer 的 Microsoft 的 JWT 验证和身份验证处理程序,并在托管域周围添加了一些验证。

它在JwtBearerOptions 上包含一个公共扩展方法UseGoogle,可让您配置处理程序以验证 Google OpenID Connect 令牌,而无需其他依赖项:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(jwt => jwt.UseGoogle(
        clientId: "<client-id-from-Google-API-console>",
        hostedDomain: "<optional-hosted-domain>"));

如果想看源码,可以找here。

【讨论】:

我很困惑我有一个 similar issue 。我试图使用您的解决方案来修复它,但我看不到您如何传递自定义范围 在请求 JWT 时指定范围。这是为了验证已经发布的 JWT。您应该在向 Google 请求 JWT 时指定您需要的范围,而不是在这里。 @khillang 如果我们将它添加到我们的 Auth 链中,它是否允许我们使用类似的东西来验证 Google AccessToken? var 结果 = 等待 HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);?如果是这样,我们是否仍在使用 Google Cookies Auth?谢谢! @Dumber_Texan2 它不验证访问令牌,它验证 ID 令牌。您需要改用GoogleJwtBearerDefaults.AuthenticationScheme @IgorLankin 在这个库中不需要客户端密码,因为它只是验证 Google 发布的现有令牌。机密(而非公开)客户将机密信息传递给 Google 以请求令牌 ?【参考方案3】:

Mikeyg36 的回答非常棒,终于帮我解决了我的 jwt 令牌问题。但是,我添加了我认为很重要的 clientId,因为您不想验证任何传入的 id 令牌。我还在 AddIdentity 中添加了“JwtBearerDefaults.AuthenticationScheme”。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Google.Apis.Auth;

namespace Some.Namespace

    public class GoogleTokenValidator : ISecurityTokenValidator
    
        private readonly string _clientId;
        private readonly JwtSecurityTokenHandler _tokenHandler;

        public GoogleTokenValidator(string clientId)
        
            _clientId = clientId;
            _tokenHandler = new JwtSecurityTokenHandler();
        

        public bool CanValidateToken => true;

        public int MaximumTokenSizeInBytes  get; set;  = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;

        public bool CanReadToken(string securityToken)
        
            return _tokenHandler.CanReadToken(securityToken);
        

        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
        
            validatedToken = null;
            try 
                var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()  Audience =  new[]  _clientId ).Result; // here is where I delegate to Google to validate
            
                var claims = new List<Claim>
                    
                        new Claim(ClaimTypes.NameIdentifier, payload.Name),
                        new Claim(ClaimTypes.Name, payload.Name),
                        new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
                        new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
                        new Claim(JwtRegisteredClaimNames.Email, payload.Email),
                        new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
                        new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
                    ;

                var principle = new ClaimsPrincipal();
                principle.AddIdentity(new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme));
                return principle;
            
            catch (Exception e)
            
                Debug.WriteLine(e);
                throw;
            
        
    

【讨论】:

以上是关于使用 AspNet Core 2.0 进行 Google JWT 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET CORE 2.0 中的 FromUri

asp.net 2.0 网站 IIS/应用程序池 出现以下问题,望高手解决

.NET Core 2.0 身份和 jwt?

Asp.net core 2.0 Jwt 不会忽略 AllowAnonymous

是否可以使用进程内 aspnet core 3 进行实时重新加载?

.net core 2.0外部登录 - 额外的个人资料信息