ASP.NET Core中基于令牌的身份validation(刷新)

我正在使用ASP.NET Core应用程序。 我试图实现基于令牌的身份validation,但无法弄清楚如何使用新的安全系统 。

我的场景:客户端请求令牌。 我的服务器应授权用户,并返回客户端在以下请求中使用的access_token。

这里有两个关于实现我所需要的伟大的文章:

  • 基于令牌的身份validation使用ASP.NET Web API 2,Owin和Identity
  • 使用JSON Web令牌

问题是 – 我不明白如何在ASP.NET Core中做同样的事情。

我的问题是:如何configurationASP.NET Core Web Api应用程序使用基于令牌的身份validation? 我应该追求什么方向? 你写了关于最新版本的任何文章,或知道我能find哪些?

谢谢!

从Matt Dekrey的精彩回答中 ,我创build了一个基于令牌的身份validation的完整工作示例,针对ASP.NET Core(1.0.1)进行了工作。 您可以在GitHub ( 1.0.0-rc1 , beta8 , beta7的替代分支) 上find完整的代码,但简而言之,重要的步骤是:

为您的应用程序生成一个密钥

在我的示例中,每次应用程序启动时都会生成一个随机密钥,您需要生成一个并将其存储在某处并将其提供给您的应用程序。 看到这个文件,我如何生成一个随机密钥,以及如何从.json文件导入它 。 正如@kspearrin的评论中所build议的那样, Data Protection API似乎是“正确”pipe理密钥的理想候选者,但是如果可能的话,我还没有弄清楚。 如果你解决的话,请提交一个pull请求!

Startup.cs – ConfigureServices

在这里,我们需要加载一个私钥用于我们的令牌签名,我们也将用它来validation令牌。 我们将密钥存储在一个类级别的variableskey ,我们将在下面的configuration方法中重用它。 TokenAuthOptions是一个简单的类,它持有TokenController中需要的签名标识,受众和发行者来创build我们的密钥。

 // Replace this with some sort of loading from config / file. RSAParameters keyParams = RSAKeyUtils.GetRandomKey(); // Create the key, and a set of token options to record signing credentials // using that key, along with the other parameters we will need in the // token controlller. key = new RsaSecurityKey(keyParams); tokenOptions = new TokenAuthOptions() { Audience = TokenAudience, Issuer = TokenIssuer, SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest) }; // Save the token options into an instance so they're accessible to the // controller. services.AddSingleton<TokenAuthOptions>(tokenOptions); // Enable the use of an [Authorize("Bearer")] attribute on methods and // classes to protect. services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​) .RequireAuthenticatedUser().Build()); }); 

我们还制定了授权政策,允许我们在我们希望保护的端点和类别上使用[Authorize("Bearer")]

Startup.cs – configuration

在这里,我们需要configurationJwtBearerAuthentication:

 app.UseJwtBearerAuthentication(new JwtBearerOptions { TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = key, ValidAudience = tokenOptions.Audience, ValidIssuer = tokenOptions.Issuer, // When receiving a token, check that it is still valid. ValidateLifetime = true, // This defines the maximum allowable clock skew - ie // provides a tolerance on the token expiry time // when validating the lifetime. As we're creating the tokens // locally and validating them on the same machines which // should have synchronised time, this can be set to zero. // Where external tokens are used, some leeway here could be // useful. ClockSkew = TimeSpan.FromMinutes(0) } }); 

TokenController

在令牌控制器中,您需要使用在Startup.cs中加载的密钥来生成签名密钥的方法。 我们已经在Startup中注册了TokenAuthOptions实例,所以我们需要在T​​okenController的构造函数中注入它:

 [Route("api/[controller]")] public class TokenController : Controller { private readonly TokenAuthOptions tokenOptions; public TokenController(TokenAuthOptions tokenOptions) { this.tokenOptions = tokenOptions; } ... 

然后,您需要在处理程序中为login终结点生成令牌,在我的示例中,我正在接受用户名和密码,并使用if语句对其进行validation,但是您需要做的关键是创build或加载声明为基础的身份,并为此生成令牌:

 public class AuthRequest { public string username { get; set; } public string password { get; set; } } /// <summary> /// Request a new token for a given username/password pair. /// </summary> /// <param name="req"></param> /// <returns></returns> [HttpPost] public dynamic Post([FromBody] AuthRequest req) { // Obviously, at this point you need to validate the username and password against whatever system you wish. if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST")) { DateTime? expires = DateTime.UtcNow.AddMinutes(2); var token = GetToken(req.username, expires); return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires }; } return new { authenticated = false }; } private string GetToken(string user, DateTime? expires) { var handler = new JwtSecurityTokenHandler(); // Here, you should create or look up an identity for the user which is being authenticated. // For now, just creating a simple generic identity. ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) }); var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() { Issuer = tokenOptions.Issuer, Audience = tokenOptions.Audience, SigningCredentials = tokenOptions.SigningCredentials, Subject = identity, Expires = expires }); return handler.WriteToken(securityToken); } 

这应该是。 只需将[Authorize("Bearer")]到您要保护的任何方法或类中,并且如果您尝试在没有令牌存在的情况下尝试访问它,则会出现错误。 如果你想返回一个401而不是500的错误,你需要注册一个自定义的exception处理程序, 就像我在这里的例子 。

  1. 为您的应用程序生成一个RSA密钥。 下面是一个非常基本的例子,但是有很多关于如何在.Net框架中处理安全密钥的信息。 我强烈build议你至less读一下它 。

     private static string GenerateRsaKeys() { RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider(2048); RSAParameters publicKey = myRSA.ExportParameters(true); return myRSA.ToXmlString(includePrivateParameters: true); } 

    将其保存到一个.xml文件并将其包含在您的应用程序中; 我将它embedded到我的DLL中,因为它是一个小型的个人项目,我认为没有人能够访问我的程序集,但是有很多原因,这不是一个好主意,所以我不提供这个例子。 最终,你必须决定什么是最适合你的项目。

    注意:有人指出ToXmlStringFromXmlString在.NET Core中不可用。 相反,您可以使用符合Core的方式(例如使用JSON RSAParameters ExportParameters(bool includePrivateParameters) ,使用RSAParameters ExportParameters(bool includePrivateParameters)void ImportParameters(RSAParameters parameters)自己保存/加载值。

  2. 创build一些我们稍后会用到的常量; 这是我所做的:

     const string TokenAudience = "Myself"; const string TokenIssuer = "MyProject"; 
  3. 将其添加到您的Startup.cs的ConfigureServices 。 稍后我们将使用dependency injection来访问这些设置。 我离开了访问RSA的XMLstream; 但我假设你有一个streamvariables访问它。

     RsaSecurityKey key; using (var textReader = new System.IO.StreamReader(stream)) { RSACryptoServiceProvider publicAndPrivate = new RSACryptoServiceProvider(); publicAndPrivate.FromXmlString(textReader.ReadToEnd()); key = new RsaSecurityKey(publicAndPrivate.ExportParameters(true)); } services.AddInstance(new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest)); services.Configure<OAuthBearerAuthenticationOptions>(bearer => { bearer.TokenValidationParameters.IssuerSigningKey = key; bearer.TokenValidationParameters.ValidAudience = TokenAudience; bearer.TokenValidationParameters.ValidIssuer = TokenIssuer; }); 
  4. build立承载authentication。 如果您使用Identity,请 UseIdentity之前执行此UseIdentity 。 请注意,任何第三方authentication行(如UseGoogleAuthentication )必须位于UseIdentity之前 。 如果您使用Identity,则不需要任何UseCookieAuthentication

     app.UseOAuthBearerAuthentication(); 
  5. 你可能想指定一个AuthorizationPolicy 。 这将允许您使用[Authorize("Bearer")]指定仅允许承载令牌作为authentication的控制器和操作。

     services.ConfigureAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationTypes(OAuthBearerAuthenticationDefaults.AuthenticationType) .RequireAuthenticatedUser().Build()); }); 
  6. 棘手的部分来了:构build令牌。 我不会在这里提供我所有的代码,但它应该足以重现。 (我在自己的代码库中有一些与这个代码无关的专有的东西。)

    这个位是从构造函数注入的; 这就是为什么我们configuration上面的选项,而不是简单地将它们传递给UseOAuthBearerAuthentication()

     private readonly OAuthBearerAuthenticationOptions bearerOptions; private readonly SigningCredentials signingCredentials; 

    然后,在你的/Token行动…

     // add to using clauses: // using System.IdentityModel.Tokens.Jwt; var handler = bearerOptions.SecurityTokenValidators.OfType<JwtSecurityTokenHandler>() .First(); // The identity here is the ClaimsIdentity you want to authenticate the user as. // You can add your own custom claims to it if you like. // You can get this using the SignInManager if you're using Identity. var securityToken = handler.CreateToken( issuer: bearerOptions.TokenValidationParameters.ValidIssuer, audience: bearerOptions.TokenValidationParameters.ValidAudience, signingCredentials: signingCredentials, subject: identity); var token = handler.WriteToken(securityToken); 

    var token是您的不记名令牌,您可以将其作为string返回给用户,以便像对承载者validation期望的那样传递。

  7. 如果您在HTML页面上以局部视图呈现此视图,并结合.Net 4.5中的仅带有承载的身份validation,则现在可以使用ViewComponent来执行相同操作。 它大部分与上面的Controller Action代码相同。

为了实现您所描述的内容,您需要一个OAuth2 / OpenID Connect授权服务器和一个为您的APIvalidation访问令牌的中间件。 Katana曾经提供OAuthAuthorizationServerMiddleware ,但在ASP.NET Core中不再存在。

我build议看一下AspNet.Security.OpenIdConnect.Server ,这是您提到的教程使用的OAuth2授权服务器中间件的一个实验性分支:有一个OWIN / Katana 3版本和一个支持这两个版本的ASP.NET Core版本net451 (.NET Desktop)和netstandard1.4 (与.NET Core兼容)。

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server

不要错过展示如何使用AspNet.Security.OpenIdConnect.ServerconfigurationOpenID Connect授权服务器的MVC Core示例以及如何validation由服务器中间件发布的encryption访问令牌: https : //github.com/aspnet-的contrib / AspNet.Security.OpenIdConnect.Server /团块的/ dev /样品/ MVC / Mvc.Server / Startup.cs

您还可以阅读这篇博文,该文章解释了如何实施资源所有者密码授权,即基本authentication的OAuth2等效function: http : //kevinchalet.com/2016/07/13/creating-your-own-openid-连接服务器与- ASOS -实施-在资源所有者密码,证书发放/

Startup.cs

 public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(); } public void Configure(IApplicationBuilder app) { // Add a new middleware validating the encrypted // access tokens issued by the OIDC server. app.UseOAuthValidation(); // Add a new middleware issuing tokens. app.UseOpenIdConnectServer(options => { options.TokenEndpointPath = "/connect/token"; // Override OnValidateTokenRequest to skip client authentication. options.Provider.OnValidateTokenRequest = context => { // Reject the token requests that don't use // grant_type=password or grant_type=refresh_token. if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType()) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedGrantType, description: "Only grant_type=password and refresh_token " + "requests are accepted by this return Task.FromResult(0); } // Since there's only one application and since it's a public client // (ie a client that cannot keep its credentials private), // call Skip() to inform the server the request should be // accepted without enforcing client authentication. context.Skip(); return Task.FromResult(0); }; // Override OnHandleTokenRequest to support // grant_type=password token requests. options.Provider.OnHandleTokenRequest = context => { // Only handle grant_type=password token requests and let the // OpenID Connect server middleware handle the other grant types. if (context.Request.IsPasswordGrantType()) { // Do your credentials validation here. // Note: you can call Reject() with a message // to indicate that authentication failed. var identity = new ClaimsIdentity(context.Options.AuthenticationScheme); identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique id]"); // By default, claims are not serialized // in the access and identity tokens. // Use the overload taking a "destinations" // parameter to make sure your claims // are correctly inserted in the appropriate tokens. identity.AddClaim("urn:customclaim", "value", OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); var ticket = new AuthenticationTicket( new ClaimsPrincipal(identity), new AuthenticationProperties(), context.Options.AuthenticationScheme); // Call SetScopes with the list of scopes you want to grant // (specify offline_access to issue a refresh token). ticket.SetScopes("profile", "offline_access"); context.Validate(ticket); } return Task.FromResult(0); }; }); } } 

project.json

 { "dependencies": { "AspNet.Security.OAuth.Validation": "1.0.0", "AspNet.Security.OpenIdConnect.Server": "1.0.0" } } 

祝你好运!

您可以使用OpenIddict来提供令牌(login),然后使用UseJwtBearerAuthentication在访问API / Controller时validation它们。

这基本上是您在Startup.cs所需的全部configuration:

ConfigureServices:

 services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() // this line is added for OpenIddict to plug in .AddOpenIddictCore<Application>(config => config.UseEntityFramework()); 

configuration

 app.UseOpenIddictCore(builder => { // here you tell openiddict you're wanting to use jwt tokens builder.Options.UseJwtTokens(); // NOTE: for dev consumption only! for live, this is not encouraged! builder.Options.AllowInsecureHttp = true; builder.Options.ApplicationCanDisplayErrors = true; }); // use jwt bearer authentication to validate the tokens app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.RequireHttpsMetadata = false; // must match the resource on your token request options.Audience = "http://localhost:58292/"; options.Authority = "http://localhost:58292/"; }); 

有一两个其他的小事情,比如你的DbContext需要从OpenIddictContext<ApplicationUser, Application, ApplicationRole, string>派生。

你可以在我的博客文章中看到一个完整的解释(包括运行的github回购): http : //capesean.co.za/blog/asp-net-5-jwt-tokens/

您可以查看OpenId连接示例,了解如何处理不同的身份validation机制,包括JWT令牌:

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples

如果您查看Cordova Backend项目,API的configuration如下所示:

 app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch => { branch.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.RequireHttpsMetadata = false; options.Audience = "localhost:54540"; options.Authority = "localhost:54540"; }); }); 

/Providers/AuthorizationProvider.cs中的逻辑和该项目的RessourceController也值得看看;)。

此外,我已经使用Aurelia前端框架和ASP.NET核心实现了基于令牌的身份validation的单页面应用程序。 还有一个信号R持续连接。 但是我没有做任何数据库实现。 代码可以在这里看到: https : //github.com/alexandre-spieser/AureliaAspNetCoreAuth

希望这可以帮助,

最好,

亚历克斯