61

ASP.NET Core JWT 认证

 5 years ago
source link: http://beckjin.com/2019/03/24/aspnet-jwt/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JWT (JSON Web Token)是一种开放标准,它以 JSON 对象的方式在各方之间安全地传输信息。通俗的说,就是通过数字签名算法生产一个字符串,然后在网络请求的中被携带到服务端进行身份认证,功能上来说和 SessionId 认证方式很像。

JWT 与 SessionId 认证对比

SessionId 认证方式一般做法是用户登录成功后,服务端生成一个 SessionId,然后将 SessionId 和 用户的关系进行存储(内存、Redis、数据库等),之后将 SessionId 写入 Cookie(一般是主域名下,方便单点登录) 或返回给调用方,后续的所有请求都携带这个 SessionId 到服务端进行身份认证。

yi2QbqF.png!web

而 JWT 最大区别是登录状态不在服务端进行存储,而是通过密钥生成一个具有有效时间的 Token 返回给前端,Token 中包含类似用户 Id 等信息,且是不允许被篡改的,之后的请求将 Token 携带到服务端进行认证,认证通过后可解析 Token 拿到用户标识进行后续操作。

U3eI7bN.png!web

JWT 构成

Header

header 典型的由两部分组成:token的类型(JWT)和算法名称(HMAC、SHA256、RSA等)

如:

{
  "alg": "HS256",
  "typ": "JWT"
}

通过 Base64 对这个 JSON 编码就得到 JWT 的第一部分。

Payload

它包含关于实体(通常是用户)和其他数据的声明,分别是 Registered、Public 和 Private 三种类型。

  • Registered claims : 预定义的声明,它们不是强制的,但是推荐。如:iss (issuer)、exp (expiration time)、sub (subject)、aud (audience) 等
  • Public claims : 可随意定义
  • Private claims : 用于在同意使用它们的各方之间共享信息

如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

通过 Base64 对这个 JSON 编码就得到 JWT 的第二部分。

Signature

Signature用来验证发送请求者身份,由前两部分加密形成。

如:

var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));

通过 Header+Payload+Signature 就得到如下结果:

famMZjF.png!web

注意:Payload 最终是可以被解析成明文的,所以在设置 Payload 时一定不能将非加密的敏感信息存储在内

JWT 使用方式

NvUvqiU.png!web

  1. 客户端到认证服务进行认证
  2. 认证成功返回 Token
  3. 客户端在请求头中加入 Authorization: Bearer {Token} 访问 API 资源

.NET Core 集成 JWT

搭建 JWTServer

  1. 创建 JWTServer(.NET Core Web API)项目 (用来进行身份认证及生成 Token)

  2. Nuget 安装 Microsoft.AspNetCore.Authentication.JwtBearer

  3. 配置文件中加入 JWT 相关参数

    "JwtSetting": {
    	"SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密钥
    	"Issuer": "jwtIssuertest", 		// 颁发者
    	"Audience": "jwtAudiencetest", 	// 接收者
    	"ExpireSeconds": 20 			// 过期时间(20s)
       }
    
  4. 添加用户登录接口,模拟身份认证

    private readonly static User User = new User
    {
    	Id = 1,
    	Name = "beck",
    	Password = "123456"
    };
    
    public async Task<User> LoginAsync(string name, string password)
    {
    	await Task.CompletedTask;
    	if (User.Name == name && User.Password == password)
    	{
    		return User;
    	}
    	return null;
    }
    
  5. 用户认证成功后获得 User 详细信息,然后生成 Token

    public string GetToken(User user)
    {
    	//创建用户身份标识,可按需要添加更多信息
    	var claims = new Claim[]
    	{
    		new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
    		new Claim("id", user.Id.ToString(), ClaimValueTypes.Integer32), // 用户id
    		new Claim("name", user.Name), // 用户名
    		new Claim("admin", user.IsAdmin.ToString(),ClaimValueTypes.Boolean) // 是否是管理员
    	};
    
    	//创建令牌
    	var token = new JwtSecurityToken(
    			issuer: _jwtSetting.Issuer,
    			audience: _jwtSetting.Audience,
    			signingCredentials: _jwtSetting.Credentials,
    			claims: claims,
    			notBefore: DateTime.Now,
    			expires: DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds)
    		);
    
    	string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
    
    	return jwtToken;
    }
    
  6. 返回 Token 信息

    {
      "Status": true,
      "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3Njg1MDUwMS1kZTk5LTRmYmYtYmVlNy01ODAxMjY5ZjNiMTgiLCJpZCI6MSwibmFtZSI6ImJlY2siLCJhZG1pbiI6dHJ1ZSwibmJmIjoxNTUzMzU2MTc1LCJleHAiOjE1NTMzNzYxNzUsImlzcyI6Imp3dElzc3VlcnRlc3QiLCJhdWQiOiJqd3RBdWRpZW5jZXRlc3QifQ.O15rMLMHADGkmhsCNAhcrCMO6c5iQzkXHfbU0jj5HaM",
      "Type": "Bearer"
    }
    
  7. 将 Token 在 https://jwt.io/ 进行解析查看效果:

    ZZ3Ubuq.png!web

搭建 TestApi

  1. 创建 TestApi(.NET Core Web API)项目 (模拟需要身份认证的 API 接口)

  2. Nuget 安装 Microsoft.AspNetCore.Authentication.JwtBearer

  3. 配置文件中加入 JWT 相关参数

    "JwtSetting": {
    	"SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密钥
    	"Issuer": "jwtIssuertest", 		// 颁发者
    	"Audience": "jwtAudiencetest" 	// 接收者
       }
    
  4. Startup 的 ConfigureServices 方法加入如下代码:

    var jwtSetting = new JwtSetting();
    Configuration.Bind("JwtSetting", jwtSetting);
    
    services
       .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
       .AddJwtBearer(options =>
       {
    	   options.TokenValidationParameters = new TokenValidationParameters
    	   {
    		   ValidIssuer = jwtSetting.Issuer,
    		   ValidAudience = jwtSetting.Audience,
    		   IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),
    		   // 默认允许 300s  的时间偏移量,设置为0
    		   ClockSkew = TimeSpan.Zero
    	   };
       });
    
  5. Startup 的 Configure 方法加入如下代码:

    app.UseAuthentication();
    
  6. 在需要认证的接口上加 [Authorize] 特性

    [HttpGet]
    [Authorize]
    public async Task<string> Get()
    {
    	await Task.CompletedTask;
    
    	return $"{_identityService.GetUserId()}:{_identityService.GetUserName()}";
    }
    
    public class IdentityService : IIdentityService
    {
    	private readonly IHttpContextAccessor _context;
    
    	public IdentityService(IHttpContextAccessor context)
    	{
    		_context = context;
    	}
    	
    	public int GetUserId()
    	{
    		var nameId = _context.HttpContext.User.FindFirst("id");
    		return nameId != null ? Convert.ToInt32(nameId.Value) : 0;
    	}
    
    	public string GetUserName()
    	{
    		return _context.HttpContext.User.FindFirst("name")?.Value;
    	}
    }
    
  7. 使用 Postman 进行测试

    • 请求头不添加 Authorization ,返回 401 状态码:

      zy6j63B.png!web

    • 请求头添加 Authorization,确保 Token 没过期 :

      IRr67vJ.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK