閱讀本文你的收獲
- 了解JWT身份認證的流程
- 了解基于JWT身份認證和Session身份認證的區(qū)別
- 學習如何在ASP.NET Core WebAPI項目中封裝JWT認證功能
在上文ASP.NET Core高級之認證與授權(一)–JWT入門-頒發(fā)、驗證令牌中演示了JWT認證的一個入門案例,本文是一個基于JWT認證的完整的前后端實現(xiàn)代碼案例。
一、基于JWT的用戶認證
JWT身份認證的流程
在認證的時候,當用戶用他們的憑證成功登錄以后,一個JSON Web Token將會被返回。此后,token就是用戶憑證了,你必須非常小心以防止出現(xiàn)安全問題。一般而言,你保存令牌的時候不應該超過你所需要它的時間。
無論何時用戶想要訪問受保護的路由或者資源的時候,用戶代理(通常是瀏覽器)都應該帶上JWT,典型的,通常放在Authorization header中,用Bearer schema。
上圖流程說明:
- 用戶攜帶用戶名和密碼請求認證
- 服務器校驗用戶賬號密碼,成功則提供一個token給客戶端
- 客戶端存儲token,并且在隨后的每一次請求中都帶著它
- 服務器校驗token有效則返回數(shù)據(jù),無效則返回401狀態(tài)碼;
二、基于JWT身份認證和Session身份認證的區(qū)別
基于Session的身份認證的缺點
-
會話信息會占用服務器
每次用戶認證通過以后,服務器需要創(chuàng)建一條記錄保存用戶信息,通常是在內存中,隨著認證通過的用戶越來越多,服務器的在這里的開銷就會越來越大。
-
難以擴展
由于Session是在服務器的內存中的,這就帶來一些擴展性的問題。
-
跨域共享難
當我們想要擴展我們的應用,讓我們的數(shù)據(jù)被多個移動設備使用時,我們必須考慮跨資源共享(如使用Redis)問題。當使用AJAX調用從另一個域名下獲取資源時,我們可能會遇到禁止請求的問題。
-
安全性差
客戶端要存Cookie來保存SessionId,所以 用戶很容易受到CSRF攻擊。
基于JWT令牌的身份認證的優(yōu)缺點
優(yōu)點:
-
簡單輕巧
JWT生成的token字符串是輕量級,json風格,比較簡單。
-
減輕服務器壓力
它是無狀態(tài)的,JWT方式將用戶狀態(tài)分散到了客戶端中,服務器或者Session中不會存儲任何用戶信息。明顯減輕服務端的內存壓力。
-
容易做分布式
沒有會話信息意味著應用程序可以根據(jù)需要擴展和添加更多的機器,而不必擔心用戶登錄的位置;
缺點:
- JWT token一旦簽發(fā),無法修改
- 無法更新token有效期,用戶登錄狀態(tài)刷新較難實現(xiàn)
- 無法銷毀一個token,服務端不能對用戶狀態(tài)進行絕對控制
- 不包含權限控制
三、JWT前后端完整實現(xiàn)關鍵代碼
開發(fā)環(huán)境:
操作系統(tǒng): Windows 10 專業(yè)版
平臺版本是:.NET 6
開發(fā)框架:ASP.NET Core WebApi、Vue2+ElementUI
開發(fā)工具:Visual Studio 2022
1. JWT的選項配置
在appsetting.json文件中,配置JWT選項參數(shù),這樣做的好處是,使用者在發(fā)布后任然可以修改JWT的參數(shù),如過期時間,密鑰等。
"JWTTokenOption": {
"Issuer": "WLW", //Token發(fā)布者
"Audience": "EveryTestOne", //Token接受者
"IssuerSigningKey": "WLW!@#%^99825949", //秘鑰可以構建服務器認可的token;簽名秘鑰長度最少16
"AccessTokenExpiresMinutes": "30" //過期時間30分鐘
}
為了在ASP.NET Core WebAPI項目中讀出JWT選項參數(shù),首先定義一個用于保存JWT選項的模型類:
/// <summary>
/// 用來保存jwt的配置信息
/// </summary>
public class JwtTokenOption
{
/// <summary>
/// Token發(fā)布者
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// oken接受者
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 秘鑰
/// </summary>
public string IssuerSigningKey { get; set; }
/// <summary>
/// 過期時間
/// </summary>
public int AccessTokenExpiresMinutes { get; set; }
}
在Program.cs中通過以下方式,讀取JWT配置選項:
//獲取jwt配置項
var jwtTokenConfig = builder.Configuration.GetSection("JWTTokenOption").Get<JwtTokenOption>();
builder.Services.AddSingleton(jwtTokenConfig); //注冊單例服務,以便后續(xù)調用
2. 配置Jwt身份認證方式
在Program.cs中進行服務注冊,配置身份驗證的模式為JwtBearer。
安裝Microsoft.AspNetCore.Authentication.JwtBearer這個nuget包
//配置JwtBearer身份認證服務
builder.Services.AddAuthentication(opts=>
{
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; //認證模式
opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; //質詢模式
})
.AddJwtBearer( //對JwtBearer進行配置
x =>
{
x.RequireHttpsMetadata = true; //設置元數(shù)據(jù)地址或權限是否需要HTTP
x.SaveToken = true;
//Token驗證參數(shù)
x.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true, //是否驗證Issuer
ValidIssuer = jwtTokenConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtTokenConfig.IssuerSigningKey)),
ValidateAudience = true,
ValidAudience = jwtTokenConfig.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1) //對token過期時間驗證的允許時間
};
//如果jwt過期,在返回的header中加入Token-Expired字段為true,前端在獲取返回header時判斷
x.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
}
);
3. 編寫生成JWT令牌的幫助類
首先,定義相關的一些模型類,如下:
/// <summary>
/// 存放Token 跟過期時間的模型類
/// </summary>
public class TnToken
{
/// <summary>
/// token字符串
/// </summary>
public string TokenStr { get; set; }
/// <summary>
/// token過期時間
/// </summary>
public DateTime Expires { get; set; }
}
/// <summary>
/// 返回信息模型類
/// </summary>
public class ResponseModel
{
/// <summary>
/// 返回碼
/// </summary>
public int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
public string Msg { get; set; }
/// <summary>
/// 數(shù)據(jù)
/// </summary>
public object Data { get; set; }
/// <summary>
/// Token信息
/// </summary>
public TnToken TokenInfo { get; set; }
}
/// <summary>
/// token工具類的接口,方便使用依賴注入,很簡單提供兩個常用的方法
/// </summary>
public interface ITokenHelper
{
/// <summary>
/// 根據(jù)一個對象通過反射提供負載,生成token
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="user"></param>
/// <returns></returns>
TnToken CreateToken<T>(T entity) where T : class;
/// <summary>
/// 根據(jù)鍵值對提供負載,生成token
/// </summary>
/// <param name="keyValuePairs"></param>
/// <returns></returns>
TnToken CreateToken(Dictionary<string, string> keyValuePairs);
}
以上接口的實現(xiàn)類如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Newtonsoft.Json;
using System.Security.Cryptography;
/// <summary>
/// Token幫助類
/// </summary>
public class TokenHelper : ITokenHelper
{
//依賴注入配置項
//private readonly IOptions<JwtTokenOption> _options;
private readonly JwtTokenOption _options;
/// <summary>
/// 構造方法
/// </summary>
/// <param name="options"></param>
public TokenHelper(JwtTokenOption options)
{
_options = options;
}
/// <summary>
/// 根據(jù)一個對象通過反射提供負載,生成token
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="user"></param>
/// <returns></returns>
public TnToken CreateToken<T>(T entity) where T : class
{
//定義聲明的集合
List<Claim> claims = new List<Claim>();
//用反射把數(shù)據(jù)提供給它
foreach (var item in entity.GetType().GetProperties())
{
object obj = item.GetValue(entity);
string value = "";
if(obj != null)
{
value = obj.ToString();
}
claims.Add(new Claim(item.Name, value));
}
//根據(jù)聲明 生成token字符串
return CreateTokenString(claims);
}
/// <summary>
/// 根據(jù)鍵值對提供負載,生成token
/// </summary>
/// <param name="keyValuePairs"></param>
/// <returns></returns>
public TnToken CreateToken(Dictionary<string, string> keyValuePairs)
{
//定義聲明的集合
List<Claim> claims = new List<Claim>();
foreach (var item in keyValuePairs)
{
claims.Add(new Claim(item.Key, item.Value));
}
//根據(jù)聲明 生成token字符串
return CreateTokenString(claims);
}
/// <summary>
/// 私有方法,用于生成Token字符串
/// </summary>
/// <param name="claims"></param>
/// <returns></returns>
private TnToken CreateTokenString(List<Claim> claims)
{
//過期時間
DateTime expires = DateTime.Now.AddMinutes(_options.AccessTokenExpiresMinutes);
var token = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims, //攜帶的荷載
notBefore: DateTime.Now, //token生成時間
expires: expires, //token過期時間
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.IssuerSigningKey)), SecurityAlgorithms.HmacSha256
)
);
return new TnToken
{
Expires = expires,
TokenStr = new JwtSecurityTokenHandler().WriteToken(token)
};
}
}
4. 在用戶登錄方法中,簽發(fā)Token
//定義實例tokenHelper實例
private readonly ITokenHelper _tokenHelper;
//構造函數(shù)注入ITokenHelper實例 略
/// <summary>
/// 登錄功能
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
public IActionResult Login(UserLoginDto user) //(1) 后端登錄:賬號密碼的驗證
{
//對輸入?yún)?shù)user進行驗證,略
//定義一個響應信息的對象
ResponseModel res = new ResponseModel();
//檢查用戶是否存在(MD5對密碼進行加密)
var userInfo = _userService.CheckUserAndPwd(user.LoginName, user.Pwd);
if (userInfo != null)
{
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>
{
{ "LoginName", user.LoginName },
{ "SuperAdmin", "true"} //假定用戶屬于SuperAdmin角色
};
res.Code = 200;
res.Msg = "登錄成功";
//(2) 后端:幫助類來生成JWT字符串,JWT字符串返回給瀏覽器
res.TokenInfo = _tokenHelper.CreateToken(keyValuePairs);
return Ok(res);
}
else
{
res.Code = 401;
res.Msg = "用戶名或密碼不正確";
return Unauthorized(res); //401的錯誤碼
}
}
5. 給API接口加身份授權鎖
在Api控制器或者方法上,加[Authorize]特性,需要引用命名空間:
using Microsoft.AspNetCore.Authorization;
- [Authorize]加在控制器上,則該控制器下所有API方法需要身份授權后才能訪問;
- [Authorize]加在方法上,則僅該方法需要身份授權后才能訪問。
另外,有一個[AllowAnonymous]特性,加在Api控制器或者方法上,允許匿名訪問該Api或者控制器下所有Api方法。
Api資源被鎖保護起來之后,如果沒有登錄直接訪問,則會報401的錯誤。在Swagger中測試截圖如下:
6. Swagger中進行Token的測試
(1)在Program.cs的配置Swagger服務:
builder.Services.AddSwaggerGen(c =>{
var basePath = AppContext.BaseDirectory; //獲取應用程序的所在目錄
//或者用下面的方式也能獲取
var basePath2 =Path.GetDirectoryName(typeof(Program).Assembly.Location);
var xmlPath = System.IO.Path.Combine(basePath, "XfTech.Demo.xml"); //拼接XML文件所在路徑
//讓Swagger顯示方法、類的XML注釋信息
c.IncludeXmlComments(xmlPath, true);
//設置Swagger文檔參數(shù)
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "XfTech.Demo",
Version = "v1",
Description = "Asp.Net Core6 WebApi開發(fā)實戰(zhàn)", //描述信息
Contact = new OpenApiContact() //開發(fā)者信息
{
Name = "物聯(lián)網(wǎng)大聯(lián)盟", //開發(fā)者姓名
Email = "99825949@qq.com", //email地址
Url = new Uri("https://blog.csdn.net/ousetuhou?type=blog") //作者的主頁網(wǎng)站
}
});
//開啟Authorize權限按鈕
c.AddSecurityDefinition("JWTBearer", new OpenApiSecurityScheme()
{
Description = "這是方式一(直接在輸入框中輸入認證信息,不需要在開頭添加Bearer) ",
Name = "Authorization", //jwt默認的參數(shù)名稱
In = ParameterLocation.Header, //jwt默認存放Authorization信息的位置(請求頭中)
Type = SecuritySchemeType.Http,
Scheme = "Bearer"
});
//定義JwtBearer認證方式二
//options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
//{
// Description = "這是方式二(JWT授權(數(shù)據(jù)將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格))",
// Name = "Authorization",//jwt默認的參數(shù)名稱
// In = ParameterLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中)
// Type = SecuritySchemeType.ApiKey
//});
//聲明一個Scheme,注意下面的Id要和上面AddSecurityDefinition中的參數(shù)name一致
var scheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "JWTBearer", //這個名字與上面的一樣
Type = ReferenceType.SecurityScheme
}
};
//注冊全局認證(所有的接口都可以使用認證)
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ scheme, Array.Empty<string>() }
});
});
#endregion
(2) 測試登錄接口,輸入正確的賬號和密碼,接口返回JWT令牌(3)點擊“Authorize”按鈕,在對話框中輸入上一步獲取到的令牌
(4)輸入令牌后關閉對話框,右邊的小鎖為鎖住的狀態(tài)。接下來調用業(yè)務模塊的Api會自動講JWT令牌攜帶上。
四、Vue前臺使用JWT認證進行安全防護
登錄頁面的布局略。以下只演示如何獲取并緩存JWT令牌,以及在每個請求的時候攜帶上JWT令牌。
1. 登錄成功后,用sessionStorage保存JWT令牌
this.$http({
url:"/api/Account/Login",
method:"post",
data:this.loginForm,
}).then((res)=>{
if(res.data.code>0){
this.$message(res.data.msg);
sessionStorage.setItem('jwtToken',res.data.tokenInfo.access_token) //保存到瀏覽器緩存
this.$router.push('home') //跳轉到首頁
}
})
2. 發(fā)送請求前,用axios攔截器添加以下請求頭
Authorization: Bearer jwt令牌字符串文章來源:http://www.zghlxwxcb.cn/news/detail-789289.html
// 在main.js中 添加請求攔截器
axios.interceptors.request.use(function (config) {
// 在發(fā)送請求之前做些什么
var tkn = sessionStorage.getItem("jwtToken");
if(tkn!="")
config.headers.Authorization = 'Bearer ' + tkn
return config;
}, function (error) {
// 對請求錯誤做些什么
return Promise.reject(error);
});
本次對這個JWT認證進行了一個完整的封裝演示。如果本文對你有幫助的話,請點贊+評論+關注,或者轉發(fā)給需要的朋友。文章來源地址http://www.zghlxwxcb.cn/news/detail-789289.html
到了這里,關于ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!