美文网首页dotNETdotnet coreASP.NET Core见识录
在 .net core 2.x 中使用jwt token保护你的

在 .net core 2.x 中使用jwt token保护你的

作者: Angeladaddy | 来源:发表于2018-12-03 14:49 被阅读10次

    上一篇文章大概是1年以前写的了, 内容有些陈旧. 最近被nodejs搞得有点崩溃, 所以就又想把.net core 拾起来.看了看最新.net core 2.1 版, jwt的使用变得更加简单了.写下来记录一下.

    1 . jwt 基础

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNYXJpbyBSb3NzaSIsImVtYWlsIjoibWFyaW8ucm9zc2lAZG9tYWluLmNvbSIsImJpcnRoZGF0ZSI6IjE5ODMtMDktMjMiLCJqdGkiOiJmZjQ0YmVjOC03ZDBkLTQ3ZTEtOWJjZC03MTY4NmQ5Nzk3NzkiLCJleHAiOjE1MTIzMjIxNjgsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjM5MzkvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo2MzkzOS8ifQ.9qyvnhDna3gEiGcd_ngsXZisciNOy55RjBP4ENSGfYI
    

    一个典型的JWT 字符串由三部分组成:

    header: 头部,meta信息和算法说明
    payload: 负荷(Claims ), 可在其中放入自定义内容, 比如, 用户身份等
    signature: 签名, 数字签名, 用来保证前两者的有效性

    三者之间由.分隔, 由Base64编码. 根据Bearer 认证规则, 添加在每一次http请求头的Authorization字段中, 这也是为什么每次这个字段都必须以Bearer jwy-token这样的格式的原因.

    2. 实战

    光说不练没有用, 让我们实战一下,首先安装 .net core 环境, 然后找个地方输入dotnet new webapi -n JWT , 新建一个名为JWT的webapi工程.

    • 首先最重要的是Startup.cs文件, 我们修改其中的ConfigureServices文件以启用JWT
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    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 JWT
    {
      public class Startup
      {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
    
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void ConfigureServices(IServiceCollection services)
        {
          services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
              options.TokenValidationParameters = new TokenValidationParameters
              {
                ValidateIssuer = true,//validate the server
                ValidateAudience = true,//ensure that the recipient of the token is authorized to receive it 
                ValidateLifetime = true,//check that the token is not expired and that the signing key of the issuer is valid 
                ValidateIssuerSigningKey = true,//verify that the key used to sign the incoming token is part of a list of trusted keys
                ValidIssuer = Configuration["Jwt:Issuer"],//appsettings.json文件中定义的Issuer
                ValidAudience = Configuration["Jwt:Issuer"],//appsettings.json文件中定义的Audience
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
              };//appsettings.json文件中定义的JWT Key
            });
    
          services.AddMvc();
        }
      }
    }
    

    然后, 我们打开项目根目录下的appsettings.json文件, 添加上面那些值

    //appsettings.json
    {
    // ...
      "Jwt": {
        "Key": "veryVerySecretKey",
        "Issuer": "http://localhost:63939/"
      }
    }
    

    还要在Startup文件中启用JWT:

     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseAuthentication(); //配置启用
                app.UseMvc();
            }
    

    这样就已经可以了, JWT就已经能在项目中正常使用了, 是不是 soeasy?

    下面我们在路由上配置jwt:
    我们创建一个新的BookController:

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    
    namespace JWT.Controllers
    {
      [Route("api/[controller]")]
      public class BooksController : Controller
      {
        [HttpGet, Authorize]//启用jwt验证
        public IEnumerable<Book> Get()
        {
          var currentUser = HttpContext.User;
          var resultBookList = new Book[] {
            new Book { Author = "Ray Bradbury",Title = "Fahrenheit 451" },
            new Book { Author = "Gabriel García Márquez", Title = "One Hundred years of Solitude" },
            new Book { Author = "George Orwell", Title = "1984" },
            new Book { Author = "Anais Nin", Title = "Delta of Venus" }
          };
    
          return resultBookList;
        }
    
        public class Book
        {
          public string Author { get; set; }
          public string Title { get; set; }
          public bool AgeRestriction { get; set; }
        }
      }
    }
    

    上面的get方法中, 我们增加了``标签, 表示此路由受到保护, 你也可以把这个标签加在类上, 这样整个api都受到保护. 但是如果我想使整个类都受到保护,但是除了一些api以外怎么办呢?答案是用AllowAnonymous标签:

    
       [Authorize]
        public class BooksController : Controller
        {
           [HttpGet, AllowAnonymous]
            public IEnumerable<Book> Get()
            {
                  //....
            }
            //.....
      }
    
    认证失败

    接下来, 我们来编写用户颁发JWT路由, 要不然我们的api没法访问了...

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    
    namespace JWT.Controllers
    {
      [Route("api/[controller]")]
      public class TokenController : Controller
      {
        private IConfiguration _config;
    
        public TokenController(IConfiguration config)
        {
          _config = config;
        }
    
        [AllowAnonymous]
        [HttpPost]
        public IActionResult CreateToken([FromBody]LoginModel login)
        {
          IActionResult response = Unauthorized();
          var user = Authenticate(login);
    
          if (user != null)
          {
            var tokenString = BuildToken(user);
            response = Ok(new { token = tokenString });
          }
    
          return response;
        }
    
        private string BuildToken(UserModel user)
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken(_config["Jwt:Issuer"],
              _config["Jwt:Issuer"],
              expires: DateTime.Now.AddMinutes(30),
              signingCredentials: creds);
    
            return new JwtSecurityTokenHandler().WriteToken(token);
         }
    
         private UserModel Authenticate(LoginModel login)
         {
            UserModel user = null;
    
            if (login.Username == "mario" && login.Password == "secret")
            {
                user = new UserModel { Name = "Mario Rossi", Email = "mario.rossi@domain.com"};
            }
            return user;
         }
    
        public class LoginModel
        {
          public string Username { get; set; }
          public string Password { get; set; }
        }
    
        private class UserModel
        {
          public string Name { get; set; }
          public string Email { get; set; }
          public DateTime Birthdate { get; set; }
        }
      }
    }
    

    这个类虽然有点长,但是核心就是BuildToken方法, 我们使用config的信息,生成了一个30分钟有效期的jwt.

    我们使用{"username": "mario", "password": "secret"}POST进行注册, 成功获取了 token

    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNYXJpbyBSb3NzaSIsImVtYWlsIjoibWFyaW8ucm9zc2lAZG9tYWluLmNvbSIsImJpcnRoZGF0ZSI6IjE5ODMtMDktMjMiLCJqdGkiOiJmZjQ0YmVjOC03ZDBkLTQ3ZTEtOWJjZC03MTY4NmQ5Nzk3NzkiLCJleHAiOjE1MTIzMjIxNjgsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjM5MzkvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo2MzkzOS8ifQ.9qyvnhDna3gEiGcd_ngsXZisciNOy55RjBP4ENSGfYI"
    }
    

    然后 ,在每次请求头中带上这个token即可,Bearer xxxxxxx 使用postman即是:

    image.png

    3. JWT中的Claims

    前面提到, JWT中的payload中, 是可以放入一些自定义信息的, 我们试试看:
    修改前面TokenController中的BuildToken方法, 将用户信息加入其中:

    private string BuildToken(UserModel user)
    {
        //添加Claims信息
        var claims = new[] {
            new Claim(JwtRegisteredClaimNames.Sub, user.Name),
            new Claim(JwtRegisteredClaimNames.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Birthdate, user.Birthdate.ToString("yyyy-MM-dd")),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };
    
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
        var token = new JwtSecurityToken(_config["Jwt:Issuer"],
          _config["Jwt:Issuer"],
          claims,//添加claims
          expires: DateTime.Now.AddMinutes(30),
          signingCredentials: creds);
    
        return new JwtSecurityTokenHandler().WriteToken(token);
    }
    

    修改BooksController进行测试:

    [Route("api/[controller]")]
    public class BooksController : Controller
    {
      [HttpGet, Authorize]
      public IEnumerable<Book> Get()
      {
        var currentUser = HttpContext.User;
        int userAge = 0;
        var resultBookList = new Book[] {
          new Book { Author = "Ray Bradbury", Title = "Fahrenheit 451", AgeRestriction = false },
          new Book { Author = "Gabriel García Márquez", Title = "One Hundred years of Solitude", AgeRestriction = false },
          new Book { Author = "George Orwell", Title = "1984", AgeRestriction = false },
          new Book { Author = "Anais Nin", Title = "Delta of Venus", AgeRestriction = true }
        };
      //取出token中的claim信息并验证, 如果用户年龄<18岁, 则去掉一些R级内容?哈哈
        if (currentUser.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
        {
          DateTime birthDate = DateTime.Parse(currentUser.Claims.FirstOrDefault(c => c.Type == ClaimTypes.DateOfBirth).Value);
          userAge = DateTime.Today.Year - birthDate.Year;
        }
    
        if (userAge < 18)
        {
          resultBookList = resultBookList.Where(b => !b.AgeRestriction).ToArray();
        }
    
        return resultBookList;
      }
    
      public class Book
      {
        public string Author { get; set; }
        public string Title { get; set; }
        public bool AgeRestriction { get; set; }
      }
    }
    

    这样就行了.其实现实世界中我们最常用的是根据Claims中的Role来判断用户权限.

    4 . CORS跨域(Cross-Origin Requests )的启用

    一般, 使用JWT的场合都是要跨域的, 因为这时候客户端千差万别, 且都不在服务器的domain上, 在.net core中启用跨域也比较简单:
    打开Startup文件, ``中添加以下内容:

       services.AddCors(options =>
                {
                    options.AddPolicy("CorsPolicy",
                        builder => builder.AllowAnyOrigin()
                          .AllowAnyMethod()
                          .AllowAnyHeader()
                          .AllowCredentials()
                    .Build());
                });
    

    Configure方法中加入:

       app.UseCors("CorsPolicy");
    

    就ok了.
    以上就是JWT在.net core2中的实现了, 希望能帮到大家.

    相关文章

      网友评论

        本文标题:在 .net core 2.x 中使用jwt token保护你的

        本文链接:https://www.haomeiwen.com/subject/ypvjcqtx.html