gpt4 book ai didi

详解ASP.NET Core Token认证

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 27 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章详解ASP.NET Core Token认证由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

令牌认证(Token Authentication)已经成为单页应用(SPA)和移动应用事实上的标准。即使是传统的B/S应用也能利用其优点。优点很明白:极少的服务端数据管理、可扩展性、可以使用单独的认证服务器和应用服务器分离.

如果你对令牌(token)不是太了解,可以看这篇文章( overview of token authentication and JWTs) 。

令牌认证在asp.net core中集成。其中包括保护Bearer Jwt的路由功能,但是移除了生成token和验证token的部分,这些可以自定义或者使用第三方库来实现,得益于此,MVC和Web api项目可以使用令牌认证,而且很简单。下面将一步一步实现,代码可以在( 源码)下载.

ASP.NET Core令牌验证 。

首先,背景知识:认证令牌,例如JWTs,是通过http 认证头传递的,例如:

?
1
2
GET /foo
Authorization: Bearer [token]

令牌可以通过浏览器cookies。传递方式是header或者cookies取决于应用和实际情况,对于移动app,使用headers,对于web,推荐在html5 storage中使用cookies,来防止xss攻击.

asp.net core对jwts令牌的验证很简单,特别是你通过header传递.

1、生成 SecurityKey,这个例子,我生成对称密钥验证jwts通过HMAC-SHA256加密方式,在startup.cs中:

?
1
2
3
// secretKey contains a secret passphrase only your server knows
var secretKey = "mysupersecret_secretkey!123" ;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));

验证 header中传递的JWTs 。

在 Startup.cs中,使用Microsoft.AspNetCore.Authentication.JwtBearer中的UseJwtBearerAuthentication 方法获取受保护的api或者mvc路由有效的jwt.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var tokenValidationParameters = new TokenValidationParameters
{
   // The signing key must match!
   ValidateIssuerSigningKey = true ,
   IssuerSigningKey = signingKey,
 
   // Validate the JWT Issuer (iss) claim
   ValidateIssuer = true ,
   ValidIssuer = "ExampleIssuer" ,
 
   // Validate the JWT Audience (aud) claim
   ValidateAudience = true ,
   ValidAudience = "ExampleAudience" ,
 
   // Validate the token expiry
   ValidateLifetime = true ,
 
   // If you want to allow a certain amount of clock drift, set that here:
   ClockSkew = TimeSpan.Zero
};
 
app.UseJwtBearerAuthentication( new JwtBearerOptions
{
   AutomaticAuthenticate = true ,
   AutomaticChallenge = true ,
   TokenValidationParameters = tokenValidationParameters
});

通过这个中间件,任何[Authorize]的请求都需要有效的jwt:

签名有效; 。

过期时间; 。

有效时间; 。

Issuer 声明等于“ExampleIssuer” 。

订阅者声明等于 “ExampleAudience” 。

如果不是合法的JWT,请求终止,issuer声明和订阅者声明不是必须的,它们用来标识应用和客户端.

在cookies中验证JWTs 。

ASP.NET Core中的cookies 认证不支持传递jwt。需要自定义实现 ISecureDataFormat接口的类。现在,你只是验证token,不是生成它们,只需要实现Unprotect方法,其他的交给System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler这个类处理.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.IdentityModel.Tokens;
 
namespace SimpleTokenProvider
{
   public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
   {
     private readonly string algorithm;
     private readonly TokenValidationParameters validationParameters;
 
     public CustomJwtDataFormat( string algorithm, TokenValidationParameters validationParameters)
     {
       this .algorithm = algorithm;
       this .validationParameters = validationParameters;
     }
 
     public AuthenticationTicket Unprotect( string protectedText)
       => Unprotect(protectedText, null );
 
     public AuthenticationTicket Unprotect( string protectedText, string purpose)
     {
       var handler = new JwtSecurityTokenHandler();
       ClaimsPrincipal principal = null ;
       SecurityToken validToken = null ;
 
       try
       {
         principal = handler.ValidateToken(protectedText, this .validationParameters, out validToken);
 
         var validJwt = validToken as JwtSecurityToken;
 
         if (validJwt == null )
         {
           throw new ArgumentException( "Invalid JWT" );
         }
 
         if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
         {
           throw new ArgumentException($ "Algorithm must be '{algorithm}'" );
         }
 
         // Additional custom validation of JWT claims here (if any)
       }
       catch (SecurityTokenValidationException)
       {
         return null ;
       }
       catch (ArgumentException)
       {
         return null ;
       }
 
       // Validation passed. Return a valid AuthenticationTicket:
       return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie" );
     }
 
     // This ISecureDataFormat implementation is decode-only
     public string Protect(AuthenticationTicket data)
     {
       throw new NotImplementedException();
     }
 
     public string Protect(AuthenticationTicket data, string purpose)
     {
       throw new NotImplementedException();
     }
   }
}

在startup.cs中调用 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var tokenValidationParameters = new TokenValidationParameters
{
   // The signing key must match!
   ValidateIssuerSigningKey = true ,
   IssuerSigningKey = signingKey,
 
   // Validate the JWT Issuer (iss) claim
   ValidateIssuer = true ,
   ValidIssuer = "ExampleIssuer" ,
 
   // Validate the JWT Audience (aud) claim
   ValidateAudience = true ,
   ValidAudience = "ExampleAudience" ,
 
   // Validate the token expiry
   ValidateLifetime = true ,
 
   // If you want to allow a certain amount of clock drift, set that here:
   ClockSkew = TimeSpan.Zero
};
 
app.UseCookieAuthentication( new CookieAuthenticationOptions
{
   AutomaticAuthenticate = true ,
   AutomaticChallenge = true ,
   AuthenticationScheme = "Cookie" ,
   CookieName = "access_token" ,
   TicketDataFormat = new CustomJwtDataFormat(
     SecurityAlgorithms.HmacSha256,
     tokenValidationParameters)
});

如果请求中包含名为access_token的cookie验证为合法的JWT,这个请求就能返回正确的结果,如果需要,你可以加上额外的jwt chaims,或者复制jwt chaims到ClaimsPrincipal在CustomJwtDataFormat.Unprotect方法中,上面是验证token,下面将在asp.net core中生成token.

ASP.NET Core生成Tokens 。

在asp.net 4.5中,这个UseOAuthAuthorizationServer中间件可以轻松的生成tokens,但是在asp.net core取消了,下面写一个简单的token生成中间件,最后,有几个现成解决方案的链接,供你选择.

简单的token生成节点 。

首先,生成 POCO保存中间件的选项. 生成类:TokenProviderOptions.cs 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using Microsoft.IdentityModel.Tokens;
 
namespace SimpleTokenProvider
{
   public class TokenProviderOptions
   {
     public string Path { get ; set ; } = "/token" ;
 
     public string Issuer { get ; set ; }
 
     public string Audience { get ; set ; }
 
     public TimeSpan Expiration { get ; set ; } = TimeSpan.FromMinutes(5);
 
     public SigningCredentials SigningCredentials { get ; set ; }
   }
}

现在自己添加一个中间件,asp.net core 的中间件类一般是这样的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
 
namespace SimpleTokenProvider
{
   public class TokenProviderMiddleware
   {
     private readonly RequestDelegate _next;
     private readonly TokenProviderOptions _options;
 
     public TokenProviderMiddleware(
       RequestDelegate next,
       IOptions<TokenProviderOptions> options)
     {
       _next = next;
       _options = options.Value;
     }
 
     public Task Invoke(HttpContext context)
     {
       // If the request path doesn't match, skip
       if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
       {
         return _next(context);
       }
 
       // Request must be POST with Content-Type: application/x-www-form-urlencoded
       if (!context.Request.Method.Equals( "POST" )
         || !context.Request.HasFormContentType)
       {
         context.Response.StatusCode = 400;
         return context.Response.WriteAsync( "Bad request." );
       }
 
       return GenerateToken(context);
     }
   }
}

这个中间件类接受TokenProviderOptions作为参数,当有请求且请求路径是设置的路径(token或者api/token),Invoke方法执行,token节点只对 POST请求而且包括form-urlencoded内容类型(Content-Type: application/x-www-form-urlencoded),因此调用之前需要检查下内容类型.

最重要的是GenerateToken,这个方法需要验证用户的身份,生成jwt,传回jwt:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private async Task GenerateToken(HttpContext context)
{
   var username = context.Request.Form[ "username" ];
   var password = context.Request.Form[ "password" ];
 
   var identity = await GetIdentity(username, password);
   if (identity == null )
   {
     context.Response.StatusCode = 400;
     await context.Response.WriteAsync( "Invalid username or password." );
     return ;
   }
 
   var now = DateTime.UtcNow;
 
   // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
   // You can add other claims here, if you want:
   var claims = new Claim[]
   {
     new Claim(JwtRegisteredClaimNames.Sub, username),
     new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
     new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(now).ToString(), ClaimValueTypes.Integer64)
   };
 
   // Create the JWT and write it to a string
   var jwt = new JwtSecurityToken(
     issuer: _options.Issuer,
     audience: _options.Audience,
     claims: claims,
     notBefore: now,
     expires: now.Add(_options.Expiration),
     signingCredentials: _options.SigningCredentials);
   var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
 
   var response = new
   {
     access_token = encodedJwt,
     expires_in = ( int )_options.Expiration.TotalSeconds
   };
 
   // Serialize and return the response
   context.Response.ContentType = "application/json" ;
   await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
}

大部分代码都很官方,JwtSecurityToken 类生成jwt,JwtSecurityTokenHandler将jwt编码,你可以在claims中添加任何chaims。验证用户身份只是简单的验证,实际情况肯定不是这样的,你可以集成 identity framework或者其他的,对于这个实例只是简单的硬编码:

?
1
2
3
4
5
6
7
8
9
10
11
private Task<ClaimsIdentity> GetIdentity( string username, string password)
{
   // DON'T do this in production, obviously!
   if (username == "TEST" && password == "TEST123" )
   {
     return Task.FromResult( new ClaimsIdentity( new System.Security.Principal.GenericIdentity(username, "Token" ), new Claim[] { }));
   }
 
   // Credentials are invalid, or account doesn't exist
   return Task.FromResult<ClaimsIdentity>( null );
}

添加一个将DateTime生成timestamp的方法:

?
1
2
public static long ToUnixEpochDate(DateTime date)
   => ( long )Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);

现在,你可以将这个中间件添加到startup.cs中了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
 
namespace SimpleTokenProvider
{
   public partial class Startup
   {
     public Startup(IHostingEnvironment env)
     {
       var builder = new ConfigurationBuilder()
         .AddJsonFile( "appsettings.json" , optional: true );
       Configuration = builder.Build();
     }
 
     public IConfigurationRoot Configuration { get ; set ; }
 
     public void ConfigureServices(IServiceCollection services)
     {
       services.AddMvc();
     }
 
     // The secret key every token will be signed with.
     // In production, you should store this securely in environment variables
     // or a key management tool. Don't hardcode this into your application!
     private static readonly string secretKey = "mysupersecret_secretkey!123" ;
 
     public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
     {
       loggerFactory.AddConsole(LogLevel.Debug);
       loggerFactory.AddDebug();
 
       app.UseStaticFiles();
 
       // Add JWT generation endpoint:
       var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
       var options = new TokenProviderOptions
       {
         Audience = "ExampleAudience" ,
         Issuer = "ExampleIssuer" ,
         SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
       };
 
       app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));
 
       app.UseMvc();
     }
   }
}

测试一下,推荐使用chrome 的postman:

?
1
2
3
POST /token
Content-Type: application/x-www-form-urlencoded
username=TEST&password=TEST123

结果: OK 。

Content-Type: application/json   {   "access_token": "eyJhb...",   "expires_in": 300 } 。

你可以使用jwt工具查看生成的jwt内容。如果开发的是移动应用或者单页应用,你可以在后续请求的header中存储jwt,如果你需要在cookies中存储的话,你需要对代码修改一下,需要将返回的jwt字符串添加到cookie中。 测试下:

详解ASP.NET Core Token认证

详解ASP.NET Core Token认证

其他方案 。

下面是比较成熟的项目,可以在实际项目中使用:

  • AspNet.Security.OpenIdConnect.Server – ASP.NET 4.x的验证中间件。
  • OpenIddict – 在identity上添加OpenId验证。
  • IdentityServer4 – .NET Core认证中间件(现在测试版本)。

下面的文章可以让你更加的了解认证:

  • Overview of Token Authentication Features
  • How Token Authentication Works in Stormpath
  • Use JWTs the Right Way!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我.

原文链接:http://www.cnblogs.com/indexlang/p/indexlang.html 。

最后此篇关于详解ASP.NET Core Token认证的文章就讲到这里了,如果你想了解更多关于详解ASP.NET Core Token认证的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com