###JWT的簡單使用 ####介紹 當今Web開發中,API的使用越來越廣泛,而API的安全性也變得越來越重要。其中,JWT(JSON Web Token)鑒權和授權是一種常見的解決方案。 本篇文章將會介紹JWT鑒權和授權的原理、實現方式以及註意事項。 ####什麼是JWT? JWT是一種基於JS ...
JWT的簡單使用
介紹
當今Web開發中,API的使用越來越廣泛,而API的安全性也變得越來越重要。其中,JWT(JSON Web Token)鑒權和授權是一種常見的解決方案。
本篇文章將會介紹JWT鑒權和授權的原理、實現方式以及註意事項。
什麼是JWT?
JWT是一種基於JSON格式的開放標準(RFC7519),用於在網路上傳遞聲明信息的一種簡潔、自包含的安全方式。JWT通常被用來在各個系統之間傳遞身份認證信息和用戶授權信息。
安裝相關 NuGet 包
在開始使用 JWT 進行授權鑒權之前,需要先安裝 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包。可以使用 Visual Studio 的 NuGet 管理器或者命令行工具進行安裝。
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
JWT的組成部分
JWT由三個部分組成:Header(頭部)、Payload(負載)和Signature(簽名)。
頭部(Header)
頭部通常由兩部分組成:令牌類型(即JWT)和指定該令牌所使用的簽名演算法。例如:
{
"alg": "HS256",
"typ": "JWT"
}
負載(Payload)
負載通常包含了需要傳遞的聲明信息,聲明信息由鍵值對組成。例如:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
其中,“sub”表示主題(subject),可以是用戶ID或其他標識符;“name”表示用戶名;“iat”表示令牌發行時間。
簽名(Signature)
簽名是對Header和Payload的內容進行數字簽名後得到的一串字元串。簽名用於驗證JWT是否被篡改或偽造。例如:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
其中,“secret”為使用該令牌的伺服器端保存的密鑰。
JWT的優點
- 簡潔:由於JWT採用了JSON格式,而JSON是一種輕量級的數據格式,因此JWT非常適合在多個服務之間傳遞信息。
- 自包含:JWT包含了所有必要的信息,因此不需要像Session那樣在伺服器端存儲用戶狀態。
- 安全:JWT使用數字簽名來保證消息完整性和真實性,並可以對負載進行加密處理。
JWT鑒權
JWT鑒權是指通過JWT來驗證用戶身份和許可權。在使用JWT鑒權時,客戶端將用戶憑證(例如用戶名和密碼)發送給伺服器,在伺服器驗證用戶憑證有效後,生成一個JWT並將其返回給客戶端。客戶端在以後的請求中攜帶這個JWT,伺服器通過驗證JWT的簽名和有效期等信息來驗證用戶身份和授權信息。
JWT鑒權流程
- 用戶登錄,向伺服器提交身份憑證(例如用戶名、密碼)。
- 伺服器驗證身份憑證的有效性。
- 如果身份憑證有效,伺服器生成一個JWT並將其返回給客戶端。
- 客戶端在以後的請求中攜帶JWT。
- 伺服器從JWT中解析出用戶ID等信息,並根據信息來驗證用戶身份和授權信息。
JWT鑒權實現
配置appsettings.json
我們需要在appsettings.json文件中配置JWT的相關信息。在您的ASP.NET Core項目中,找到appsettings.json文件,並添加以下配置:
"Authentication": {
"SecretKey": "yourIssuer",
"Issuer": "yourAudience",
"Audience": "yourSecretKey"
},
添加 JWT 鑒權服務
在ASP.NET Core中,可以使用JwtBearer認證方案來驗證JWT。首先,在Startup.cs文件中添加以下代碼:
public void ConfigureServices(IServiceCollection services)
{
// 添加JWT身份驗證服務
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:SecretKey"]); // 從appsettings.json讀取Jwt配置
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = Configuration["Authentication:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Authentication:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
};
});
// 其他服務配置
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其他中間件配置...
app.UseAuthentication();
app.UseAuthorization();
}
上面代碼中,我們向依賴註入容器中註冊了一個身份驗證方案,名稱為 JwtBearerDefaults.AuthenticationScheme,表示使用 JWT 進行身份驗證。然後,我們使用 AddJwtBearer 擴展方法,將 JWT 鑒權服務添加到應用程式中。
在 AddJwtBearer 方法中,我們需要配置 TokenValidationParameters 來驗證 JWT。其中,ValidateIssuer、ValidIssuer、ValidateAudience、ValidAudience 和 ValidateLifetime 屬性用於驗證 JWT 中的發行人、接收方、有效期等信息。IssuerSigningKey 屬性表示密鑰,用於對 JWT 進行數字簽名。最後,ValidateIssuerSigningKey 屬性用於驗證 JWT 的簽名是否正確。
生成JWT
在ASP.NET Core中,可以使用JwtSecurityToken類來創建JWT。例如:
var signingAlgorithm = SecurityAlgorithms.HmacSha256;
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,"user_id"),
new Claim(ClaimTypes.Role,"Admin"),
new Claim("UserId","12"),
};
var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
var signingKey = new SymmetricSecurityKey(secretByte);
var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm);
var token = new JwtSecurityToken(
issuer: _configuration["Authentication:Issuer"],
audience: _configuration["Authentication:Audience"],
claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddDays(1),
signingCredentials
);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
在這裡,我們首先創建了一個聲明(Claims)列表,其中包含用戶ID、角色信息。然後,我們指定了JWT的過期時間和簽名演算法,並使用SymmetricSecurityKey類來指定密鑰。最後,我們使用JwtSecurityTokenHandler類將token轉換為字元串形式的jwt。
驗證JWT
public bool ValidateAccessToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_jwtConfig.SecretKey);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _jwtConfig.Issuer,
ValidAudience = _jwtConfig.Audience,
IssuerSigningKey = new SymmetricSecurityKey(key)
}, out var validatedToken);
}
catch (Exception)
{
return false;
}
return true;
}
}
上面代碼中,我們使用 JwtSecurityTokenHandler 類來驗證 JWT 的真實性和完整性。其中,我們使用 TokenValidationParameters 來配置驗證參數,包括是否驗證 JWT 發行人、接收方、有效期等信息,以及使用哪個密鑰對其進行數字簽名。如果驗證通過,則返回 true,否則返回 false。
JWT授權
JWT授權是指根據JWT中包含的聲明信息來驗證用戶是否具有訪問特定資源的許可權。在使用JWT授權時,我們在JWT中添加了一些聲明信息,例如用戶所屬角色、許可權等,伺服器可以通過這些信息來驗證用戶是否有權訪問特定資源。
JWT授權流程
- 用戶登錄,向伺服器提交身份憑證(例如用戶名、密碼)。
- 伺服器驗證身份憑證的有效性。
- 如果身份憑證有效,伺服器生成一個JWT並將其返回給客戶端。
- 客戶端在以後的請求中攜帶JWT。
- 伺服器從JWT中解析出用戶信息和聲明信息,並根據信息來驗證用戶是否有權訪問特定資源。
JWT授權實現
用戶登錄
創建傳入DTO:LoginDto 、返回DTO:LoginOutDto類
public class LoginDto
{
public string UserName { get; set; }
public string Pwd { get; set; }
}
public class LoginOutDto
{
public int Code { get; set; }
public string Msg { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
}
在用戶登錄時,我們需要對用戶提供的用戶名和密碼進行驗證,並生成訪問令牌和刷新令牌。下麵是一個簡單的示例,演示如何在ASP.NET Core中實現用戶登錄驗證,並生成JWT令牌。
[HttpPost("login")]
public LoginOutDto Login([FromBody] LoginDto input)
{
var dto = new LoginOutDto();
try
{
if (input.UserName != "admin" || input.Pwd != "bb123456")
{
dto.Code = 500;
dto.Msg = "用戶名或密碼不正確";
dto.access_token = string.Empty;
return dto;
}
// 生成訪問令牌
var accessToken = _jwtService.GenerateAccessToken();
// 生成刷新令牌
var refreshToken = _jwtService.GenerateRefreshToken();
dto.Code = 200;
dto.Msg = "登錄成功";
dto.access_token = accessToken;
dto.refresh_token = refreshToken;
}
catch (Exception ex)
{
dto.Code = 500;
dto.Msg = "登錄失敗:" + ex.Message;
}
return dto;
}
在上面的示例中,我們通過調用_jwtService.GenerateAccessToken和_jwtService.GenerateRefreshToken方法來生成訪問令牌和刷新令牌,並將刷新令牌保存到資料庫或其他持久化存儲中,以便後續使用。
刷新令牌
在用戶登錄後,訪問令牌會在一定時間後過期,此時用戶需要使用刷新令牌來獲取新的訪問令牌,而無需重新登錄。下麵是一個簡單的示例,演示如何在ASP.NET Core中實現刷新令牌功能。
[HttpPost("refresh")]
public IActionResult RefreshToken(UserModel model)
{
// 驗證刷新令牌是否有效
var isValidRefreshToken = ValidateAccessToken(model.RefreshToken);
if (!isValidRefreshToken)
{
return BadRequest(new { message = "Invalid refresh token" });
}
// 生成新的訪問令牌
var accessToken = _jwtService.GenerateAccessToken(model);
// 返回新的訪問令牌給客戶端
return Ok(new
{
access_token = accessToken
});
}
在上面的示例中,我們通過調用_jwtService.GenerateAccessToken方法來生成新的訪問令牌,並將其返回給客戶端。在生成新的訪問令牌時,我們可以使用之前保存的用戶信息,例如用戶名等
完整代碼
下麵是一個包含生成 JWT,解析 JWT,鑒權,授權和策略的完整示例。請註意,此示例僅供參考,請根據實際需求進行修改。
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:SecretKey"]);
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = Configuration["Authentication:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Authentication:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
};
});
// 註入IJwtService服務
services.AddSingleton<IJwtService, JwtService>();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.Demo", Version = "v1" });
});
}
// 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", "JWT.Demo v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
// 身份驗證
app.UseAuthentication();
// 授權
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
IJwtService&JwtService
public interface IJwtService
{
/// <summary>
/// 生成JWT
/// </summary>
/// <returns></returns>
string GenerateAccessToken();
/// <summary>
/// 刷新Token
/// </summary>
/// <returns></returns>
string GenerateRefreshToken();
/// <summary>
/// 驗證JWT
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
bool ValidateAccessToken(string token);
}
public class JwtService : IJwtService
{
private readonly IConfiguration _configuration;
public JwtService(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// 生成JWT
/// </summary>
/// <returns></returns>
public string GenerateAccessToken()
{
var signingAlgorithm = SecurityAlgorithms.HmacSha256;
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,"user_id"),
new Claim(ClaimTypes.Role,"Admin"),
new Claim("UserId","12"),
};
var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
var signingKey = new SymmetricSecurityKey(secretByte);
var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm);
var token = new JwtSecurityToken(
issuer: _configuration["Authentication:Issuer"],
audience: _configuration["Authentication:Audience"],
claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddDays(1),
signingCredentials
);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
return tokenStr;
}
/// <summary>
/// 刷新Token
/// </summary>
/// <returns></returns>
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
/// <summary>
/// 驗證JWT
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public bool ValidateAccessToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Authentication:Issuer"],
ValidAudience = _configuration["Authentication:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key)
}, out var validatedToken);
}
catch (Exception)
{
return false;
}
return true;
}
}
AuthenticateController.cs
[ApiController]
[Route("auth")]
public class AuthenticateController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IJwtService _jwtService;
public AuthenticateController(IConfiguration configuration, IJwtService jwtService)
{
_configuration = configuration;
_jwtService = jwtService;
}
[HttpPost("login")]
public LoginOutDto Login([FromBody] LoginDto input)
{
var dto = new LoginOutDto();
try
{
if (input.UserName != "admin" || input.Pwd != "bb123456")
{
dto.Code = 500;
dto.Msg = "用戶名或密碼不正確";
dto.access_token = string.Empty;
return dto;
}
// 生成訪問令牌
var accessToken = _jwtService.GenerateAccessToken();
// 生成刷新令牌
var refreshToken = _jwtService.GenerateRefreshToken();
dto.Code = 200;
dto.Msg = "登錄成功";
dto.access_token = accessToken;
dto.refresh_token = refreshToken;
}
catch (Exception ex)
{
dto.Code = 500;
dto.Msg = "登錄失敗:" + ex.Message;
}
return dto;
}
[HttpPost("refresh")]
public IActionResult RefreshToken(string token)
{
// 驗證刷新令牌是否有效
var isValidRefreshToken = _jwtService.ValidateAccessToken(token);
if (!isValidRefreshToken)
{
return BadRequest(new { message = "Invalid refresh token" });
}
// 生成新的訪問令牌
var accessToken = _jwtService.GenerateAccessToken();
// 返回新的訪問令牌給客戶端
return Ok(new
{
access_token = accessToken
});
}
}
註意事項
-
密鑰管理:在使用JWT時,密鑰是非常重要的,泄露密鑰會導致安全問題。因此,密鑰的生成、存儲和更新都必須謹慎處理。
-
過期時間:在生成JWT時,要指定合適的過期時間,避免JWT過期後仍然可以使用。
-
簽名演算法:簽名演算法的選擇很重要,不同的簽名演算法具有不同的安全性和效率。建議採用HMAC+
-
SHA256或RSA演算法。
- 不要存儲敏感信息:JWT雖然安全,但仍然存在被盜用的可能性。因此,在生成JWT時,應避免將敏感信息(例如密碼、信用卡號等)存儲在負載中。
- 使用HTTPS:在使用JWT時,建議採用HTTPS協議來保證通訊的安全性。
- 謹慎處理“記住我”功能:在實現“記住我”功能時,需要謹慎處理,避免密鑰泄露或用戶憑證被盜用。
結論
在.NET 5 中使用 JWT 進行授權鑒權是一種安全、可靠的身份驗證方式。通過添加 JWT 鑒權服務、使用 Authorize 屬性啟用 JWT 授權、生成和驗證 JWT、使用 UseAuthentication 和 UseAuthorization 中間件來啟用身份驗證和授權,併為不同的 API 設置不同的授權策略,可以輕鬆地實現 JWT 的授權鑒權功能。