内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用
我正在使用ASP.NET Core应用程序。我试图实现基于令牌的身份验证,但无法弄清楚如何使用新的安全系统。
我的场景: 客户端请求令牌。我的服务器应授权用户并返回客户端在以下请求中使用的access_token。
这里有两篇关于实现我需要的精彩文章:
问题是 - 对我来说,如何在ASP.NET Core中做同样的事情并不明显。
我的问题是:如何配置ASP.NET Core Web Api应用程序以使用基于令牌的身份验证?我应该追求什么方向?你有没有写过关于最新版本的文章,或者知道我能找到哪些文章?
我创建了一个基于令牌的身份验证的完整工作示例,针对ASP.NET Core(1.0.1)进行了工作。你可以找到完整的代码在这个仓库在GitHub上(替代分支1.0.0-RC1,beta8,β7的),但在短暂的,重要的步骤是:
为应用程序生成密钥
在我的示例中,每次应用程序启动时都会生成一个随机密钥,需要生成一个并将其存储在某处并将其提供给您的应用程序。查看这个文件,了解我如何生成随机密钥以及如何从.json文件导入它。Data Protection API似乎是“正确”管理密钥的理想人选,但我还没有制定出如果可能的话。如果你解决了问题,请提交一个拉取请求!
Startup.cs - ConfigureServices
在这里,我们需要加载一个用于我们的令牌签名的私钥,我们也将在验证令牌时使用它们来验证令牌。我们将密钥存储在类级变量中key
,我们将在下面的配置方法中重用该变量。TokenAuthOptions是一个简单的类,它持有我们在TokenController中创建密钥所需的签名身份,受众和签发者。
// 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 - 配置
在这里,我们需要配置JwtBearerAuthentication:
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 - i.e. // 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; } ...
然后,需要在处理程序中为登录端点生成令牌,在我的示例中,我使用用户名和密码并使用if语句验证这些令牌,但您需要做的关键是创建或加载声明为基础的身份并为此生成令牌:
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的错误,你需要注册一个自定义的异常处理程序,就像我在这里的示例中那样。
private static string GenerateRsaKeys() { RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider(2048); RSAParameters publicKey = myRSA.ExportParameters(true); return myRSA.ToXmlString(includePrivateParameters: true); }
将其保存到一个.xml文件并将其包含在应用程序中; 我将它嵌入到我的DLL中,因为它是一个小型的个人项目,我认为任何人都无法访问我的程序集,但有很多理由说明这不是一个好主意,所以我没有在这里提供这个例子。最终,你必须决定什么是最适合你的项目的。
注:有人指出,ToXmlString
并FromXmlString
没有在.NET中提供的核心。相反,可以使用RSAParameters ExportParameters(bool includePrivateParameters)
和void ImportParameters(RSAParameters parameters)
Core一致的方式自行保存/加载值,例如使用JSON。const string TokenAudience = "Myself"; const string TokenIssuer = "MyProject";
ConfigureServices
。稍后我们将使用依赖注入来访问这些设置。我正在离开访问RSA xml流; 但我假设你有一个stream
变量访问它。
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; });
UseIdentity
行之前执行此操作。请注意,任何第三方认证线,比如UseGoogleAuthentication
,必须走之前的UseIdentity
线。UseCookieAuthentication
如果您使用Identity,则不需要任何操作。
app.UseOAuthBearerAuthentication();
AuthorizationPolicy
。这将允许指定仅允许使用承载令牌作为认证的控制器和操作[Authorize("Bearer")]
。
services.ConfigureAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationTypes(OAuthBearerAuthenticationDefaults.AuthenticationType) .RequireAuthenticatedUser().Build()); });
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
是不记名令牌 - 可以将其作为字符串返回给用户,以便按照对承载身份验证的期望进行传递。ViewComponent
执行相同操作。它大部分与上面的Controller Action代码相同。