从 ASP.NET Core 中的 API 读取 JWT 令牌

Posted

技术标签:

【中文标题】从 ASP.NET Core 中的 API 读取 JWT 令牌【英文标题】:Reading JWT Token from API in ASP.NET Core 【发布时间】:2019-08-03 08:45:10 【问题描述】:

我的设置:我已经创建并运行了一个 WebAPI 解决方案,该解决方案针对源(当前为 db)执行用户名和密码的身份验证。这会生成 JWT 令牌并将其返回给请求应用程序(ASP.NET Core 2.2 应用程序)。

大多数解决方案都谈到保护 WebAPI 公开的方法,但我的方法是仅通过 WebAPI 进行身份验证。各个应用程序需要接受令牌,以便他们可以确定授权。

现在的问题是:从 WebAPI 读取令牌的最佳方法是什么(我已经做过),验证它,然后将其存储给任何/所有控制器以知道有一个经过身份验证的用户(通过 Authorize属性)只要令牌有效?

再调试一下,似乎我的令牌没有被添加到标题中。我看到这条调试消息:

过滤器“Microsoft.AspNet.Mvc.Filters.AuthorizeFilter”处的请求授权失败

代码更新2 - 获取 JWT 的代码:

        var client = _httpClientFactory.CreateClient();
        client.BaseAddress = new Uri(_configuration.GetSection("SecurityApi:Url").Value);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        //login
        Task<HttpResponseMessage> response = ValidateUserAsync(client, username, password);
        Task<Core.Identity.TokenViewModel> tokenResult = response.Result.Content.ReadAsAsync<Core.Identity.TokenViewModel>();

        if (!response.Result.IsSuccessStatusCode)
        
            if (tokenResult != null && tokenResult.Result != null)
            
                ModelState.AddModelError("", tokenResult.Result.ReasonPhrase);
            
            else
            
                ModelState.AddModelError("", AppStrings.InvalidLoginError);
            
            return View();
        

        JwtSecurityToken token = new JwtSecurityToken(tokenResult.Result.Token);
        int userId;

        if (int.TryParse(token.Claims.First(s => s.Type == JwtRegisteredClaimNames.NameId).Value, out userId))
        
            //load app claims
            Core.Identity.UserInfo userInfo = Core.Identity.UserLogin.GetUser(_identityCtx, userId);
            Core.Identity.UserStore uStore = new Core.Identity.UserStore(_identityCtx);
            IList<Claim> claims = uStore.GetClaimsAsync(userInfo, new System.Threading.CancellationToken(false)).Result;
            claims.Add(new Claim(Core.Identity.PowerFleetClaims.PowerFleetBaseClaim, Core.Identity.PowerFleetClaims.BaseUri));

            ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme);
            ClaimsPrincipal principal = new ClaimsPrincipal(claimsIdentity);

            //complete
            AuthenticationProperties authProperties = new AuthenticationProperties();
            authProperties.ExpiresUtc = token.ValidTo;
            authProperties.AllowRefresh = false;
            authProperties.IsPersistent = true;

            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, tokenResult.Result.Token);
            //var stuff = HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, principal, authProperties);
        
        else
        
            ModelState.AddModelError("", AppStrings.InvalidLoginError);
            return View();
        

        return RedirectToAction("Index", "Home");

启动:

private void ConfigureIdentityServices(IServiceCollection services)
    
        services.ConfigureApplicationCookie(options => options.LoginPath = "/Login");

        //authentication token
        services.AddAuthentication(opt =>
        
            opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        ).AddCookie(opt =>
        
            opt.LoginPath = "/Login";
            opt.LogoutPath = "/Login/Logoff";
            opt.Cookie.Name = Configuration.GetSection("SecurityApi:CookieName").Value;
        ).AddJwtBearer(options =>
        
            options.SaveToken = true;
            options.RequireHttpsMetadata = false;

            options.TokenValidationParameters = new TokenValidationParameters()
            
                ValidateAudience = true,
                ValidAudience = Configuration.GetSection("SecurityApi:Issuer").Value,
                ValidateIssuer = true,
                ValidIssuer = Configuration.GetSection("SecurityApi:Issuer").Value,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.GetSection("SecurityApi:Key").Value)),
                ValidateLifetime = true
            ;
        );

        Core.Startup authStart = new Core.Startup(this.Configuration);
        authStart.ConfigureAuthorizationServices(services);
    

授权:

public void ConfigureAuthorizationServices(IServiceCollection services)
    
        services.AddDbContext<Identity.IdentityContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SecurityConn")));
        services.AddScoped<DbContext, Identity.IdentityContext>(f =>
        
            return f.GetService<Identity.IdentityContext>();
        );

        services.AddIdentityCore<Identity.UserInfo>().AddEntityFrameworkStores<Identity.IdentityContext>().AddRoles<Identity.Role>();
        services.AddTransient<IUserClaimStore<Core.Identity.UserInfo>, Core.Identity.UserStore>();
        services.AddTransient<IUserRoleStore<Core.Identity.UserInfo>, Core.Identity.UserStore>();
        services.AddTransient<IRoleStore<Core.Identity.Role>, Core.Identity.RoleStore>();

        services.AddAuthorization(auth =>
        
            auth.AddPolicy(JwtBearerDefaults.AuthenticationScheme, new AuthorizationPolicyBuilder().AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build());
            auth.AddPolicy(PFBaseClaim, policy => policy.RequireClaim(Identity.PFClaims.BaseUri));
        );
    

【问题讨论】:

所以本质上你的 WebAPI 是一个身份提供者。在您的其他 API 中,您需要设置某种承载令牌身份验证。此配置必须具备验证令牌所需的一切。有几种方法可以做到这一点..但是。如果这是一个新项目,我强烈建议您看看 Identity Server。它是一个身份提供者,可提供您所需的一切,此外它还实现了 OAuth 和 OpenId Connect,因此您被覆盖了。然后在您的其他 API 上添加身份验证非常简单。 这里展示了一些关于如何启用不记名令牌认证developer.okta.com/blog/2018/03/23/…的示例 不完全是身份服务器,只是为了进行身份验证。没有其他 API 用于安全性。这是 10 多年前没有使用任何 ASPNET 身份表的现有数据库。它甚至有自己的索赔一代。我想做的是使用 JWT 编写更现代的身份验证版本,并能够使用现有程序获取声明。 如果您已经提供了一种在用户通过身份验证后返回 jwt 令牌的方法,那么只需在其他 API 中验证该令牌即可。 docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/… 我一定遗漏了一些明显的东西,因为标记为授权的控制器不会获取任何显示用户已通过身份验证的内容。令牌是否被放入标题中?曲奇饼?如何验证应用是否已获取令牌并可以使用? 【参考方案1】:

最后,我的方法是使用安全 cookie 和基本声明来证明用户已通过身份验证。

private void ConfigureAuthentication(IServiceCollection 服务) services.ConfigureApplicationCookie(options => options.LoginPath = "/Login");

        //authentication token
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opt =>
        
            opt.LoginPath = "/Login";
            opt.AccessDeniedPath = "/Login";
            opt.LogoutPath = "/Login/Logoff";
            opt.Cookie.Name = Configuration.GetSection("SecurityApi:CookieName").Value;
        ).AddJwtBearer(options =>
        
            options.SaveToken = true;

            options.TokenValidationParameters = new TokenValidationParameters()
            
                ValidateAudience = true,
                ValidAudience = Configuration.GetSection("SecurityApi:Issuer").Value,
                ValidateIssuer = true,
                ValidIssuer = Configuration.GetSection("SecurityApi:Issuer").Value,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.GetSection("SecurityApi:Key").Value)),
                ValidateLifetime = true
            ;
        );
    

在登录时:

            AuthenticationProperties authProperties = new AuthenticationProperties();
        authProperties.ExpiresUtc = token.ValidTo;
        authProperties.AllowRefresh = false;
        authProperties.IsPersistent = true;

        HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userStore.CreateAsync(user).Result, authProperties);

        return RedirectToAction("Index", "Home");

【讨论】:

以上是关于从 ASP.NET Core 中的 API 读取 JWT 令牌的主要内容,如果未能解决你的问题,请参考以下文章

从 Axios 请求返回 ASP.NET Core API 中的下载文件

这是从 ASP.NET Core Web API 中的 EF Core 5 获得的啥样的响应 [关闭]

ASP.NET Core JWT 和声明

从 ASP.NET Core Web API 中的控制器访问用户身份

如何从数据库中读取加密值并将其与 ASP.NET Core 中的另一个值进行比较?

在 ASP.NET Core Web api 中读取 JSON 值