ASP.NET Core中基于令牌的身份validation
我正在使用ASP.NET Core应用程序。 我试图实现基于令牌的身份validation,但无法弄清楚如何使用新的安全系统为我的情况。 我通过例子,但他们没有帮助我很多,他们正在使用cookieauthentication或外部身份validation(GitHub,微软,Twitter)。
我的场景是什么:angularjs应用程序应该请求/token
url传递用户名和密码。 WebApi应授权用户并返回将由angularjs应用程序在以下请求中使用的access_token
。
我发现了一篇关于在当前版本的ASP.NET中使用ASP.NET Web API 2,Owin和Identity的基于令牌的身份validation实现我所需要的精彩文章。 但对于我如何在ASP.NET Core中做同样的事情并不明显。
我的问题是:如何configurationASP.NET Core WebApi应用程序使用基于令牌的身份validation?
更新:
我已经通过并closures了我所知道的安全漏洞,并提供了新的代码。 我希望这可以帮助你!
-
为您的应用程序生成一个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中,因为它是一个小型的个人项目,我认为没有人可以访问我的程序集,但是有很多原因,这不是一个好主意,所以我不提供这个例子。 最终,你必须决定什么是最适合你的项目。
注意:在别处指出,
ToXmlString
和FromXmlString
在.NET Core中不可用。 相反,您可以使用符合Core的方式(例如使用JSONRSAParameters ExportParameters(bool includePrivateParameters)
,使用RSAParameters ExportParameters(bool includePrivateParameters)
和void ImportParameters(RSAParameters parameters)
自己保存/加载值。 -
创build一些我们稍后会用到的常量; 这是我所做的:
const string TokenAudience = "Myself"; const string TokenIssuer = "MyProject";
-
将其添加到您的Startup.cs的
ConfigureServices
。 稍后我们将使用dependency injection来访问这些设置。 我离开了访问RSA的XMLstream; 但我假设你有一个stream
variables访问它。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; });
-
build立承载authentication。 如果您使用Identity,请在
UseIdentity
行之前执行此UseIdentity
。 请注意,任何第三方authentication行(如UseGoogleAuthentication
)必须位于UseIdentity
行之前 。 如果您使用Identity,则不需要任何UseCookieAuthentication
。app.UseOAuthBearerAuthentication();
-
你可能想指定一个
AuthorizationPolicy
。 这将允许您使用[Authorize("Bearer")]
指定仅允许承载令牌作为authentication的控制器和操作。services.ConfigureAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationTypes(OAuthBearerAuthenticationDefaults.AuthenticationType) .RequireAuthenticatedUser().Build()); });
-
棘手的部分来了:构build令牌。 我不会在这里提供我所有的代码,但它应该足以重现。 (我在自己的代码库中有一些与这个代码无关的专有的东西。)
这个位是从构造函数注入的; 这就是为什么我们configuration上面的选项,而不是简单地将它们传递给UseOAuthBearerAuthentication()
private readonly OAuthBearerAuthenticationOptions bearerOptions; private readonly SigningCredentials signingCredentials;
然后,在你的
/Token
行动…var handler = bearerOptions.SecurityTokenValidators.OfType<System.IdentityModel.Tokens.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期望的那样传递。 -
如果您在HTML页面上以局部视图呈现此视图,并结合.Net 4.5中的仅带有承载的身份validation,则现在可以使用
ViewComponent
来执行相同操作。 它大部分与上面的Controller Action代码相同。
从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实例,所以我们需要在TokenController的构造函数中注入它:
[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处理程序, 就像我在这里的例子 。
您可以查看OpenId连接示例,了解如何处理不同的身份validation机制,包括JWT令牌:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples
如果您查看Cordova Backend项目,API的configuration如下所示:
// Create a new branch where the registered middleware will be executed only for non API calls. app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api")), branch => { // Insert a new cookies middleware in the pipeline to store // the user identity returned by the external identity provider. branch.UseCookieAuthentication(new CookieAuthenticationOptions { AutomaticAuthenticate = true, AutomaticChallenge = true, AuthenticationScheme = "ServerCookie", CookieName = CookieAuthenticationDefaults.CookiePrefix + "ServerCookie", ExpireTimeSpan = TimeSpan.FromMinutes(5), LoginPath = new PathString("/signin"), LogoutPath = new PathString("/signout") }); branch.UseGoogleAuthentication(new GoogleOptions { ClientId = "560027070069-37ldt4kfuohhu3m495hk2j4pjp92d382.apps.googleusercontent.com", ClientSecret = "n2Q-GEw9RQjzcRbU3qhfTj8f" }); branch.UseTwitterAuthentication(new TwitterOptions { ConsumerKey = "6XaCTaLbMqfj6ww3zvZ5g", ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI" }); });
/Providers/AuthorizationProvider.cs中的逻辑和该项目的RessourceController也值得看看;)。
或者,你也可以使用下面的代码来validation令牌(也有一个代码片段,使之与signalR一起工作):
// Add a new middleware validating access tokens. app.UseOAuthValidation(options => { // Automatic authentication must be enabled // for SignalR to receive the access token. options.AutomaticAuthenticate = true; options.Events = new OAuthValidationEvents { // Note: for SignalR connections, the default Authorization header does not work, // because the WebSockets JS API doesn't allow setting custom parameters. // To work around this limitation, the access token is retrieved from the query string. OnRetrieveToken = context => { // Note: when the token is missing from the query string, // context.Token is null and the JWT bearer middleware will // automatically try to retrieve it from the Authorization header. context.Token = context.Request.Query["access_token"]; return Task.FromResult(0); } }; });
对于发行令牌,您可以使用openId Connect服务器软件包,如下所示:
// Add a new middleware issuing access tokens. app.UseOpenIdConnectServer(options => { options.Provider = new AuthenticationProvider(); // Enable the authorization, logout, token and userinfo endpoints. //options.AuthorizationEndpointPath = "/connect/authorize"; //options.LogoutEndpointPath = "/connect/logout"; options.TokenEndpointPath = "/connect/token"; //options.UserinfoEndpointPath = "/connect/userinfo"; // Note: if you don't explicitly register a signing key, one is automatically generated and // persisted on the disk. If the key cannot be persisted, an exception is thrown. // // On production, using a X.509 certificate stored in the machine store is recommended. // You can generate a self-signed certificate using Pluralsight's self-cert utility: // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip // // options.SigningCredentials.AddCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75"); // // Alternatively, you can also store the certificate as an embedded .pfx resource // directly in this assembly or in a file published alongside this project: // // options.SigningCredentials.AddCertificate( // assembly: typeof(Startup).GetTypeInfo().Assembly, // resource: "Nancy.Server.Certificate.pfx", // password: "Owin.Security.OpenIdConnect.Server"); // Note: see AuthorizationController.cs for more // information concerning ApplicationCanDisplayErrors. options.ApplicationCanDisplayErrors = true // in dev only ...; options.AllowInsecureHttp = true // in dev only...; });
编辑:我已经实现了使用Aurelia前端框架和ASP.NET核心的基于令牌的身份validation实现单页面应用程序。 还有一个信号R持续连接。 但是我没有做任何数据库实现。 代码可以在这里看到: https : //github.com/alexandre-spieser/AureliaAspNetCoreAuth
希望这可以帮助,
最好,
亚历克斯
看一下OpenIddict–这是一个新的项目(写作的时候),可以很容易地configurationJWT令牌的创build和在ASP.NET 5中刷新令牌。令牌的validation由其他软件处理。
假设您使用Identity
with Entity Framework
,最后一行是您要添加到ConfigureServices
方法的内容:
services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() .AddOpenIddictCore<Application>(config => config.UseEntityFramework());
在Configure
,您可以设置OpenIddict来为JWT令牌提供服务:
app.UseOpenIddictCore(builder => { // 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; });
您还可以在Configure
中Configure
令牌validation:
// use jwt bearer authentication app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.RequireHttpsMetadata = false; options.Audience = "http://localhost:58292/"; options.Authority = "http://localhost:58292/"; });
有一两个其他小事情,比如你的DbContext需要从OpenIddictContext派生。
你可以在这篇博文中看到完整的解释: http : //capesean.co.za/blog/asp-net-5-jwt-tokens/