概是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");
作者:Angeladaddy
链接:https://www.jianshu.com/p/00078a4906be