編輯于 2020/03/15
修改于 2020/04/02
環境 Asp. Net Core WebAPI 3.1
JWT介紹
jwt全稱JSON Web Token,是目前最流行的跨域授權認證解決方案。
jwt是一個記錄著用戶身份信息的令牌,訪問api時持有這個令牌就可以訪問api,這就是jwt。
JWT官網
- 授權:當用戶登陸成功時,返回jwt的Token,后續的每次請求都將攜帶Jwt發放的token,來實現認證并返回所請求的數據。
</br> - 認證:因JWT簽名使用公鑰私鑰對,只要私鑰不丟失,就可以確認發放token的人,而且使用了計算簽名,可以有效的判斷驗證內容是否被修改。
JWT Token結構
HEADER.PAYLOAD.SIGNATURE
-
HEADER
頭 -
PAYLOAD
載荷 -
SIGNATURE
簽名
-
HEADER 頭
HEADER
包含token的元數據,使用的加密算法,和簽名的類型,如下所示,加密類型是JWT
,算法是HMAC SHA-256
{"alg":"HS256","typ":"JWT"}
再通過base64算法編碼,形成JWT頭
</br> -
PAYLOAD 載荷
PAYLOAD
也是一個Json,用來存放需要傳遞的數據,JWT官方解釋了7個字段可以使用。- iss (issuer):簽發人
- exp (expiration time):過期時間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時間
- iat (Issued At):簽發時間
- jti (JWT ID):編號
同時也可以自定義添加字段,如
{ "Account": "Tuser", "Name": "我叫Tuser", "Age": 18 }
需要注意的是,
PAYLOAD
包含的字段是通過Base64編碼的,所以任何獲取到token的人都可以讀取到里面的內容
</br> -
SIGNATURE 簽名
SIGNATURE
部分是對前兩部分的簽名,防止數據篡改。
首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
下面顯示了一個JWT,它具有先前的頭和??有效負載編碼,并使用機密簽名。
JWT官網提供了解碼器,大家可以自行嘗試
JWT內容
JWT運行詳解
一般在身份驗證中用戶請求登陸成功后,后端將返回JWT Token給前端,其中包含了用戶的部分可見信息。
當前端請求受到請求限制的資源時,前端在請求頭中帶著JWT Token進行請求,如Authorization: Bearer <token>
</br>
在請求受保護的路由將檢查Authorization標頭中的有效JWT,如果JWT包含必要的數據,則可減能可以少查詢數據庫以進行某些操作的需要。
</br>
使用Jwt身份驗證,那么跨域資源共享將不會成為問題,因為它不使用cookie。
</br>
Asp .Net WebAPI中使用Jwt
一. 創建. Net Core WebAPI 3.1項目并安裝NuGet包+前置工作
創建項目
這里就不演示了
</br>-
安裝的NuGet包
Jwt 包
也可以通過進行雙擊項目在包還原下添加,不明白的可以博主最后面提供的源碼
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.2" />
</br> -
創建Utils工具類庫
- 創建. net core 類庫并命名為Utils
- 創建Helper文件夾并添加Appsettings.cs文件
- 添加Appsettings代碼
Appsettings.cs
using Microsoft.Extensions.Configuration; using Microsoft.Extensions. Configuration.Json; namespace NetCore.Blogs.Demo.Utils { /// <summary> /// appsettings.json操作類 /// </summary> public class Appsettings { static IConfiguration Configuration { get; set; } static string contentPath { get; set; } public Appsettings(string contentPath) { string Path = "appsettings.json"; //如果你把配置文件 是 根據環境變量來分開了,可以這樣寫 //Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"; Configuration = new ConfigurationBuilder() .SetBasePath(contentPath) .Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//這樣的話,可以直接讀目錄里的json文件,而不是 bin 文件夾下的,所以不用修改復制屬性 .Build(); } /// <summary> /// 封裝要操作的字符 /// </summary> /// <param name="sections">節點配置</param> /// <returns></returns> public static string app(params string[] sections) { try { if (sections.Any()) { return Configuration[string.Join(":", sections)]; } } catch (Exception) { } return ""; } } }
-
創建完后Utils類庫結構如下
Utils結構 -
最后在Startup.cs中注入Appsettings類
注入cs中注入Appsettings類
完成后,就可以通過Appsettings.app(string[]) 傳入一個數組進行訪問appsettings.json配置文件了
二. 創建Jwt授權認證
重頭戲來了!
-
在
appsettings.json
中添加Jwt字段- Issuer(發行人)
- Audience(受眾)
- SecurityKey(秘鑰)
"JwtSettings": { "Issuer": "zyknow", "Audience": "audience", "SecurityKey": "zyknowzyknowzyknowzyknowzyknowzyknow" }
</br>
-
在webApi項目中創建Extensions文件夾并創建
AuthorizationSetup.cs
類,這樣把在Startup.cs做的操作提出來,這樣Startup.cs看起來更簡潔些,如圖所示,并且粘貼代碼
Extensions結構AuthorizationSetup.cs
public static class AuthorizationSetup { public static void AddAuthorizationSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); // 配置策略授權 services.AddAuthorization(o => { // 添加策略,使用時在方法上標注[Authorize(Policy ="AdminPolicy")],就會驗證請求token中的ClaimTypes.Role是否包含了Admin o.AddPolicy("AdminPolicy", o => { //ClaimTypes.Role == Admin o.RequireRole("Admin").Build(); //ClaimTypes.Role == Admin 或者 == User //o.RequireRole("Admin","User").Build(); //ClaimTypes.Role == Admin 并且 == User ,關于添加多個角色策略,在Login控制器中 //o.RequireRole("Admin").RequireRole("User").Build(); }); //只有User的策略 o.AddPolicy("onlyUserPolicy", o => { o.RequireRole("User").Build(); }); //User和Admin都可以訪問的策略 o.AddPolicy("UserOrAdminPolicy", o => { o.RequireRole("User", "Admin").Build(); }); //User并且是Admin才能請求的策略 o.AddPolicy("UserAndAdminPolicy", o => { o.RequireRole("User").RequireRole("Admin").Build(); }); }); string key = Appsettings.app(new string[] { "JwtSettings", "SecurityKey" }); string issuer = Appsettings.app(new string[] { "JwtSettings", "Issuer" }); string audience = Appsettings.app(new string[] { "JwtSettings", "Audience" }); SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)); //秘鑰的長度有要求,必須>=16位 services.AddAuthentication("Bearer").AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters() { //是否秘鑰認證 ValidateIssuerSigningKey = true, IssuerSigningKey = securityKey, //秘鑰 //是否驗證發行人 ValidateIssuer = true, ValidIssuer = issuer, //這個字符串可以隨便寫,就是發行人 //是否驗證訂閱 ValidateAudience = true, ValidAudience = audience, //是否驗證過期時間 RequireExpirationTime = true, ValidateLifetime = true, }; }); } }
</br>
-
注冊Jwt到Startup.cs中
ConfigureServicesConfigure
</br>
-
創建Login控制器并創建GetToken方法
創建Login控制器
這個控制器是用來發放Jwt Token的,通過把請求中攜帶的userRole添加到Jwt授權中并返回(實際應該是查詢數據庫,然后返回對應的權限等級之類的,比如admin,SuperAdmin,User等)
注意一下代碼中自定義的Jwt字段,后面會提到
[Route("api/[controller]")] [ApiController] [AllowAnonymous] public class LoginController : ControllerBase { /// <summary> /// 頒發令牌接口 /// </summary> /// <returns></returns> [HttpGet(nameof(GetJwtToken))] public string GetJwtToken(string userRole) { //獲取在配置文件中獲取Jwt屬性設置 string key = Appsettings.app(new string[] { "JwtSettings", "securityKey" }); string issuer = Appsettings.app(new string[] { "JwtSettings", "Issuer" }); string audience = Appsettings.app(new string[] { "JwtSettings", "Audience" }); //創建授權的token類 SecurityToken securityToken = new JwtSecurityToken( issuer: issuer, //簽發人 audience: audience, //受眾 signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)), SecurityAlgorithms.HmacSha256), //秘鑰 //創建過期時間 expires: DateTime.Now.AddHours(1), //過期時間 一小時之后過期 //自定義JWT字段 claims: new Claim[] { new Claim(ClaimTypes.Role,userRole), //把模擬請求的角色權限添加到Role中 //下面的都是自定義字段,可以任意添加到Claim作為信息共享 new Claim("Name","我叫Tuser"), new Claim("Age","18"), } ); //返回請求token return JsonConvert.SerializeObject(new {token = new JwtSecurityTokenHandler().WriteToken(securityToken)}); } }
</br>
-
開啟調試,看看是否能正確請求到Token
這里給大家推薦一下國人開發的谷歌瀏覽器調試插件,功能跟Postman一樣,非常便攜還是中文apizza進入正題,使用Http請求工具進行請求Login/GetJwtToken方法
請求GetJwtToken成功可以看到成功的獲取到了Token
我們可以復制token在Jwt官網中解析看看
Jwt官網解析Token接下來輸入我們在appsettings.json中的SecurityKey字段
SecurityKey字段內容有效令牌效驗成功了!有效令牌!
通過在PAYLOAD定義的公共屬性(若沒有私鑰偽造令牌),可以很好的實現跨域信息共享
</br> -
在控制器中使用Jwt授權認證
在創建項目時自動生成的WeatherForecastController.cs
控制器中添加如下代碼[ApiController] [Route("[controller]")] [Authorize] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet] [Route(nameof(AllGet))] [AllowAnonymous] //沒有token也能訪問 public IEnumerable<WeatherForecast> AllGet() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } [HttpGet] [Route(nameof(AllTokenGet))] [Authorize] //只需要有效token public IEnumerable<WeatherForecast> AllTokenGet() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } [HttpGet] [Route(nameof(AdminPolicy))] [Authorize(Policy = "AdminPolicy")] //需要請求攜帶token的策略為Admin才能訪問 public IEnumerable<WeatherForecast> AdminPolicy() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } [HttpGet] [Route(nameof(onlyUserPolicy))] [Authorize(Policy = "onlyUserPolicy")] //需要請求攜帶token的策略為Admin才能訪問 public IEnumerable<WeatherForecast> onlyUserPolicy() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } [HttpGet] [Route(nameof(AdminRequireMent))] [Authorize(Policy = "AdminRequireMent")] //自定義授權 public IEnumerable<WeatherForecast> AdminRequireMent() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } }
</br>
-
開啟調試
這里就不一個個方法給大家演示了,有興趣的同學可以自己下載demo進行演示
這里只給大家演示一下成功示例-
不攜帶Token請求,無法請求
不攜帶token的請求 -
在請求頭中攜帶token時請注意格式如下!
Bearer認證格式
攜帶沒有權限的token請求
先請求login獲取token,注意看,這里使用的User
注意看,這里使用的User
復制token,再請求AdminPolicy接口,可以發現狀態碼為403,請求成功,但是權限錯誤,所以請求不到數據
請求成功,但是權限錯誤 -
最后,使用Admin生成的token來請求
請求成功
-
自定義授權處理
這部分主要是講解Jwt授權認證的自定義授權處理環節
-
創建PolicyRequirement文件夾并創建
AdminRequirement.cs
類和MustRoleAdminHandler.cs
類</br>
AdminRequirement.cs
public class AdminRequirement : IAuthorizationRequirement { //這里定義的字段是在Login GetToken方法授權時確定的 public string Name { get; set; } }
</br>
MustRoleAdminHandler.cs
//繼承AuthorizationHandler<AdminRequirement> public class MustRoleAdminHandler : AuthorizationHandler<AdminRequirement> { //重寫授權認證方法,每次jwt認證都會進入這個方法,可以開啟斷點調試查看 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement) { //這里獲取的是請求時AdminRequirement類中在jwt包含的字段 Console.WriteLine(requirement); context.Succeed(requirement); return Task.CompletedTask; } }
</br>
-
在
AuthorizationSetup.cs
中添加如下代碼
添加代碼位置// 基于自定義處理策略 o.AddPolicy("AdminRequireMent", o => { o.Requirements.Add(new AdminRequirement() { Name = "zyknow" });//完全自定義 });
//依賴注入自定義處理策略 services.AddSingleton<IAuthorizationHandler, MustRoleAdminHandler>();
</br>
-
在
WeatherForecastController.cs
中添加使用自定義授權的方法
自定義授權方法[HttpGet(nameof(AdminRequireMent))] [Authorize(Policy = "AdminRequireMent")] //自定義授權 public IEnumerable<WeatherForecast> AdminRequireMent() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
</br>
-
接著啟動程序,斷點調試,帶Jwt Token請求方法
發送請求進入斷點,可以獲取到自定義授權策略的類,這里直接返回成功了,主要是只是為了演示,在這里可以做自己的邏輯處理
進入斷點
源碼
下載Jwt分支即可