.NET Core JWT 认证

JWT 介绍

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

JWT 与 SessionId 认证对比

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

SessionId

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

JWT

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 就得到如下结果:

Token

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

JWT 使用方式

JWT Access API
  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/ 进行解析查看效果:

    Token Decoded

搭建 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 状态码:

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

    Success

参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352