《Asp.Net Core3 + Vue3入坑教程》 – 5..Net Core 3升級成 .Net 5 & JWT
簡介
《Asp.Net Core3 + Vue3入坑教程》 此教程適合新手入門或者前後端分離嘗試者。可以根據圖文一步一步進操作編碼也可以選擇直接查看源碼。每一篇文章都有對應的源碼
本文將 .Net Core 3升級成 .Net 5
目錄
《Asp.Net Core3 + Vue3入坑教程》系列教程目錄
Asp.Net Core後端項目
- 後端項目搭建與Swagger配置步驟
- 配置CROS策略解決跨域問題
- AutoMapper & Restful API & DI
- EF Core & Postgresql
- (本文).Net Core 3升級成 .Net 5 & JWT
- (暫未發表敬請期待…)
Vue3 前端項目
暫未發表敬請期待…
本文簡介
本文為《Asp.Net Core3 + Vue3入坑教程》系列教程的後端第五篇 – .Net Core 3升級成 .Net 5 & JWT。上文已經為Simple項目增加了EF Core與Postgresql資料庫的連接,本文繼續為Simple項目增加JWT(JSON Web Token)的應用,目標是讓除了用戶請求認證介面之外的其餘請求都需要帶著JWT!在使用之前先將SKD的版本升級成 .Net 5。
JWT詳解參考 //jwt.io/introduction
把Simple項目從 .Net Core 3升級成 .Net 5 & 使用JWT
.Net Core 3升級成 .Net 5
上官網下載 .Net 5 SDK
//dotnet.microsoft.com/download/visual-studio-sdks?utm_source=getdotnetsdk&utm_medium=referral
確保VS版本支援 .net 5 SDK(如果是2019的話,升級成最新的即可)
打開項目,右鍵解決方案,打開項目屬性
將目標框架調整成 .NET 5.0
更新Nuget包
項目應用JWT
在ServiceProvider文件夾下新建JWT擴展類
程式碼如下:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Simple_Asp.Net_Core.ServiceProvider
{
public static class JWT
{
public static void AddJWT(this IServiceCollection services)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.RequireHttpsMetadata = false;
o.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
return Task.CompletedTask;
},
};
o.TokenValidationParameters = new TokenValidationParameters
{
// 是否驗證失效時間
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
ValidateAudience = true,
// 這裡採用動態驗證的方式,在重新登陸時,刷新token,舊token就強制失效了
AudienceValidator = AudienceValidator,
// 是否驗證Issuer
ValidateIssuer = false,
// 是否驗證SecurityKey
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Const.SecurityKey))
};
});
}
private static bool AudienceValidator(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
return Audiences.IsNewestAudience(audiences.FirstOrDefault());
}
}
}
在ServiceProvider文件夾下新建新建類Audiences.cs和Const.cs
程式碼如下:
using System;
using System.Collections.Generic;
namespace Simple_Asp.Net_Core.ServiceProvider
{
public static class Audiences
{
private static IDictionary<string, string> _audiences = new Dictionary<string, string>();
public static string UpdateAudience(string userNo)
{
if (string.IsNullOrWhiteSpace(userNo))
return string.Empty;
var audience = $"{userNo}_{DateTime.Now}";
_audiences[userNo] = audience;
return audience;
}
public static bool IsNewestAudience(string audience)
{
if (string.IsNullOrWhiteSpace(audience))
return false;
var userName = audience.Split('_')[0];
if (!_audiences.ContainsKey(userName))
return false;
else
return _audiences[userName] == audience;
}
}
}
namespace Simple_Asp.Net_Core.ServiceProvider
{
internal class Const
{
public const string Domain = "//localhost:81";
public const string SecurityKey = "Simple_Asp.Net_Core";
}
}
在類中直接引入nuget包,或者使用Nuget包管理工具引入Microsoft.AspNetCore.Authentication.JwtBearer
Startup.cs 配置調整,增加認證與授權配置
程式碼如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;
using Simple_Asp.Net_Core.Data;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
namespace Simple_Asp.Net_Core
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit //go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddJWT();
services.AddDbContext<CommanderContext>(options =>
options.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=123456"));
services.AddCORS();
services.AddMvc();
services.AddSwagger();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddScoped<ICommanderRepo, SqlCommanderRepo>();
services.AddControllers().AddNewtonsoftJson(s =>
{
s.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
});
}
app.UseCors("CorsTest");
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}
接下來就是應用了
首先我們在 CommandsController 控制器上增加特性 [Authorize]
然後啟動項目,可以發現,swagger上發出的請求出現了401錯誤
401的錯誤就是沒有許可權
HTTP401狀態詳解 //developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/401
現在需要為swagger的請求增加Token
首先增加JWT獲取介面,新建控制器OAuthController.cs
當前模擬用戶資訊獲取與用戶資訊校驗用戶校驗
Action增加[AllowAnonymous]特性,讓此介面可以接收任何的請求
程式碼如下:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Simple_Asp.Net_Core.Dtos;
using Simple_Asp.Net_Core.Extensions;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
namespace Simple_Asp.Net_Core.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OAuthController : ControllerBase
{
[HttpPost]
[AllowAnonymous]
public IActionResult Authenticate(string name, string password)
{
// 此處需補充用戶校驗與用戶具體資訊獲取...
var user = new UserProviderDto(name, password);
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(Const.SecurityKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = Audiences.UpdateAudience(user.Name),
Subject = user.GetClaimsIdentity(),
Expires = DateTime.UtcNow.AddDays(0.5),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
access_token = tokenString
});
}
}
}
在Dto文件夾下增加用戶Dto UserProviderDto.cs
程式碼如下:
namespace Simple_Asp.Net_Core.Dtos
{
public class UserProviderDto
{
public UserProviderDto(string name, string password)
{
Name = name;
Password = password;
}
public string ID { get; set; }
/// <summary>
/// 用戶名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 手機號
/// </summary>
public string Phone { get; set; }
/// <summary>
/// 電子郵箱
/// </summary>
public string Mail { get; set; }
public string Password { get; set; }
}
}
新建 Extensions 文件夾 、 新建ClaimLoginUserExtensions 擴展類
程式碼如下:
using Newtonsoft.Json;
using Simple_Asp.Net_Core.Dtos;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
namespace Simple_Asp.Net_Core.Extensions
{
public static class ClaimLoginUserExtensions
{
private const string USER = "User";
public static ClaimsIdentity GetClaimsIdentity(this UserProviderDto user)
{
return new ClaimsIdentity(new Claim[]
{
new Claim(USER, JsonConvert.SerializeObject(user))
});
}
public static UserProviderDto GetLoginUser(this IEnumerable<Claim> claims)
{
var user = JsonConvert.DeserializeObject<UserProviderDto>(claims.Get(USER));
return user;
}
public static string Get(this IEnumerable<Claim> claims, string claimType)
{
return claims.Where(v => v.Type == claimType).First().Value;
}
}
}
啟動項目,調用api/OAuth介面獲取Token
得到Token
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyIjoie1wiSURcIjpudWxsLFwiTmFtZVwiOlwiYWRtaW5cIixcIlBob25lXCI6bnVsbCxcIk1haWxcIjpudWxsLFwiUGFzc3dvcmRcIjpcIjEyMzQ1NlwifSIsIm5iZiI6MTYxNDIzOTAwNSwiZXhwIjoxNjE0MjgyMjA1LCJpYXQiOjE2MTQyMzkwMDUsImF1ZCI6ImFkbWluXzIwMjEvMi8yNSDmmJ_mnJ_lm5sgMTU6NDM6MjUifQ.yrjK8qX45mNOQ3taecIc-QVaBDlN4QUOdBPRExvpejk"
將Token賦到我們的請求上,在swagger上可以直接配置,點擊 Authorize 按鈕
將Token附上,注意開頭為Bearer (Bearer後面接著一個空格!):
實例如下:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyIjoie1wiSURcIjpudWxsLFwiTmFtZVwiOlwiYWRtaW5cIixcIlBob25lXCI6bnVsbCxcIk1haWxcIjpudWxsLFwiUGFzc3dvcmRcIjpcIjEyMzQ1NlwifSIsIm5iZiI6MTYxNDIzOTAwNSwiZXhwIjoxNjE0MjgyMjA1LCJpYXQiOjE2MTQyMzkwMDUsImF1ZCI6ImFkbWluXzIwMjEvMi8yNSDmmJ_mnJ_lm5sgMTU6NDM6MjUifQ.yrjK8qX45mNOQ3taecIc-QVaBDlN4QUOdBPRExvpejk
再點擊 Authorize 按鈕
調用/api/Commands請求 – 請求成功
程式碼編寫與配置已經全部完成
可以利用JWT在線解析工具(//jwt.io/#debugger-io) 將前面獲取的JWT的資訊解析出來
總結
本文為Simple項目增加JWT(JSON Web Token)的應用,除了用戶請求認證介面之外的其餘請求都需要帶著JWT,本文還簡單的實現了用戶單一登入,擴展類JWT.cs的AudienceValidator方法會判斷用戶使用的Token是否是最新的,如果用戶重複登入則舊的Token會失效!目前只是簡單的使用了靜態變數來保存Token的資訊,可以結合具體情況將Token保存至Redis或者資料庫中。
JWT的更多配置可以參考官方API資料 //docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer?view=aspnetcore-5.0
GitHub源碼
注意:源碼調試過程中如果出現xml文件路徑錯誤,需要參照第一章(後端項目搭建與Swagger配置步驟)Swagger配置「配置XML 文檔文件」步驟,取消勾選然後再選中 ,將XML路徑設置成與你的電腦路徑匹配!
//github.com/Impartsoft/Simple_Asp.Net_Core/tree/master/Simple_Asp.Net_Core 5.SDK Update %26 JWT
參考資料
部落格JWT(推薦學習) //www.cnblogs.com/7tiny/archive/2019/06/13/11012035.html
jwt官方資料 //jwt.io