
NG是什么意思:深度解析與應用
"alg": "HS256",
"typ": "JWT"
}
2. 有效載荷
JWT的有效負載部分包含了我們希望在兩個通信方之間傳遞的關于某個實體的實際數據或聲明信息。有效負載通常包含用戶數據:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
3. 簽名
最后,簽名用于驗證 JWT 是否未被更改或操縱。它通過使用標頭中指定的算法對編碼的標頭、有效負載和密鑰進行簽名來實現此目的。如果JWT中的數據被更改,服務器在檢測時會發現這一變化并拒絕接受。
JWT的防篡改特性使其成為雙方之間安全交換信息的優選方案。由于令牌已經過簽名,我們可以確認發件人的身份,并檢查令牌中的數據是否已被更改。
使用 JWT 的典型授權流如下所示:
設置 Json Web 令牌身份驗證可以相當簡單地完成。讓我們看一個例子。
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.IdentityModel.Tokens
Install-Package Microsoft.IdentityModel.JsonWebTokens
2. 在 Program.cs 中配置 Json Web Token 鑒權服務。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var key = "this is my custom Secret key for authentication"; // This should be stored securely, e.g., in environment variables
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
首先,我們定義一個將用于令牌簽名的密鑰。
注意:為了確保密鑰的安全,應當將其妥善存儲。雖然在此處為了演示方便以純文本形式展示,但在實際生產應用程序中,必須確保密鑰的安全存儲與訪問。例如通過環境變量或 Secret Management 服務。
然后,我們配置身份驗證服務,并指明希望使用JSON Web Token(JWT)。我們設置為驗證令牌是否使用了正確的密鑰進行簽名(validate_issuer_signing_key: true
)。這一步驟至關重要,因為它能確保令牌未被篡改,且確實來自一個受信任的來源。最后,我們使用之前定義的密鑰(secret key)來指定簽名的密鑰。
3.為Login Endpoint創建一個控制器,使用System.IdentityModel.Tokens.Jwt
命名空間。
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace JsonWebTokens.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly string _key;
public AuthController(IConfiguration config)
{
_key = "this is my custom Secret key for authentication";
}
[HttpPost("login")]
public IActionResult Login([FromBody] UserLogin userLogin)
{
if (userLogin.Username == "test" && userLogin.Password == "password")
{
var token = GenerateJwtToken(userLogin.Username);
return Ok(new { token });
}
return Unauthorized();
}
private string GenerateJwtToken(string username)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_key));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: null,
audience: null,
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
public class UserLogin
{
public string Username { get; set; }
public string Password { get; set; }
}
}
在此示例中,一個簡單的 POST 終端節點接受正文中的對象。然后,我們檢查提供的憑證是否與預期匹配。在實際的 UserLogin
生產應用程序中,您通常會從數據庫中讀取用戶,并根據數據庫中存儲的哈希版本檢查提供的密碼。若憑證驗證通過,我們將生成一個令牌,并將其返回給客戶端。該令牌的有效負載中會包含用戶名信息,并且設置其過期時間為30分鐘。
4. 使用授權保護端點
現在,我們可以繼續為要保護的端點添加授權:
[Route("api/[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
[Authorize]
public IActionResult Get()
{
return Ok(new { message = "This is a protected endpoint" });
}
}
我們使用屬性(attribute)來修飾那些我們希望進行保護的controller,以此指示這些controller應使用我們之前定義的認證配置。如果請求中未提供有效的令牌,用戶將被拒絕訪問,并收到401狀態碼(未授權)。具體做法是在controller類上添加[Authorize]
屬性。
JSON Web Token(JWT)特別適用于需要精細控制授權機制的場景。通過令牌的有效負載,我們可以提取與用戶相關的信息,并據此限制對某些資源的訪問,因此,它成為了一種理想且極為流行的授權技術。
OAuth 2.0 是一種開放標準,旨在允許應用程序代表用戶安全訪問托管在第三方服務上的服務或資源。它得到了廣泛的應用,幾乎可以被視為在線授權的行業標準。OAuth 2.0 定義了多種流程或“方案”,用于指導在不同場景下如何處理授權問題。這些流也稱為授權類型,并指示應如何針對各種情況處理授權。
OAuth 2.0 只是一個授權協議,不處理用戶身份驗證。它旨在提供對某些資源的訪問,并使用訪問令牌來實現此目的。在此類場景中,JWT(JSON Web Token)是最常被采用的方式。因此,JWT 和 OAuth 2.0 并不是兩種截然不同的、互斥的技術。相反,JWT 是一種可以在 OAuth 2.0 流程中集成和使用的格式。
OAuth 2.0 中的角色是指 OAuth 系統的不同組件。了解這些很重要。
如上所述,OAuth 2.0 提供了不同的授權類型或流程來處理各種授權情況。其中一種授權類型是 Client Credentials Grant,我們將在本文中重點介紹。此流程適用于機器對機器(M2M)通信,其中不涉及用戶的直接參與。這是一種非常普遍的方法,您可能已經在各種場景中遇到過類似的應用。在客戶端憑證授予方案中,客戶端代表自己行事,這意味著客戶端本身需要訪問特定資源。例如,這可能是需要訪問外部 API(資源服務器)以獲取數據以進行內部處理的后端應用程序(客戶端)。
客戶端憑據授權非常常見,也許是最簡單的授權類型。典型的流程如下所示:
在這個小型 .NET 示例中,我們將創建一個同時充當授權服務器和資源服務器的應用程序。通常,這兩個實體是分開的,但對于規模較小且集成度更高的應用程序,組合方法可能非常有效,并且會降低復雜性和延遲。在決定采用哪種方法時,請根據您的具體情況,在組合方法與分離方法之間做出權衡。
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.IdentityModel.Tokens
Install-Package Microsoft.IdentityModel.JsonWebTokens
2. 再次在 Program.cs 中創建必要的 JWT 配置
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var key = "this is my custom Secret key for authentication"; // This should be stored securely, e.g., in environment variables
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://localhost:7232",
ValidAudience = "https://localhost:7232",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
builder.Services.AddAuthorization();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
在這個方案中,我們啟用了ValidateIssuer
和ValidateAudience
選項。這通過確保我們的應用程序將頒發者和接收者(受眾)的信息編碼到令牌中,為我們的令牌增加了額外的安全層級。通過驗證發行者和受眾,我們能夠使得潛在攻擊者更難濫用被盜的令牌。在您事先了解將與您的應用程序進行交互的特定應用程序的情況下,這一步驟尤為重要。
3. 創建 Token 終端節點。
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace OAuth2._0.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
private const string ClientId = "1";
private const string ClientSecret = "secret";
private const string Issuer = "https://localhost:7232";
private const string Audience = "https://localhost:7232";
private const string Key = "this is my custom Secret key for authentication";
[HttpPost("token")]
public IActionResult Token([FromForm] string client_id, [FromForm] string client_secret)
{
if (client_id == ClientId && client_secret == ClientSecret)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(Key);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("sub", client_id) }),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = Issuer,
Audience = Audience,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
access_token = tokenString,
token_type = "Bearer",
expires_in = 3600, // 1 hour in seconds
});
}
return BadRequest("Invalid client credentials");
}
}
}
在生產應用程序中,通常會有一個底層系統存儲 Client ID 和 Client Secret。隨后,這兩個值(指發行者和受眾的標識符)將被授予那些希望訪問資源的可信系統(即客戶端)。在這個簡單的示例中,我們直接對這兩個值進行了硬編碼,但請注意,在實際的生產應用程序中,應該采用更為妥善的方式來處理這些值。
我們在請求中接收client_id
和client_secret
,并校驗這兩個值是否與我們的預期相匹配。如果校驗通過,我們將繼續生成JWT令牌,并向其中添加必要的聲明信息。特別地,在這個場景下,我們會添加Issuer
(發行者)和Audience
(受眾)聲明。這里提到的multipart/form-data
通常指的是請求的內容類型,它允許我們以表單格式提交數據,包括client_id
和client_secret
等信息。
響應將按以下格式返回給客戶端:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmJmIjoxNzIzMDE4MDM5LCJleHAiOjE3MjMwMjE2MzksImlhdCI6MTcyMzAxODAzOSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzIzMiIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjcyMzIifQ.0HpnhsU-Ud2zO8owCHeK5xcSIDJzU4OTLmi0LsifMx0",
"token_type": "Bearer",
"expires_in": 3600
}
在這里,我們可以看到授權服務器已向我們(客戶端)頒發了一個 Bearer 令牌,該令牌將在一小時后過期。
4. 現在可以使用 Token Access 受保護的資源。
[Route("api/[controller]")]
[ApiController]
public class ResourceController : ControllerBase
{
[HttpGet]
[Authorize]
public IActionResult Get()
{
return Ok("Protected resource data");
}
}
OAuth 2.0 是一個行業標準框架,用于處理各種場景中的授權。在上面的簡單示例中,我們已經演示了如何在典型的機器對機器場景中處理授權,但這只是眾多用例之一。OAuth 2.0 可用于多種環境,包括 Web 應用程序、移動應用程序和 IoT 設備,無論平臺如何,都可以提供統一的授權方法。OAuth 2.0因其有據可查且被廣泛應用,為開發人員提供了更便捷的實現途徑。此外,還有眾多針對不同編程語言的工具和庫可供使用,這些工具和庫能夠簡化OAuth 2.0的集成流程。
在前兩章中,我們了解了一些更復雜且可擴展的授權機制。對于那些沒有相同級別伸縮性和靈活性需求的小型應用程序而言,基本身份驗證提供了一種更為簡單直接的方法來實施對Web資源的訪問控制,盡管它在安全性和可擴展性方面相對較弱。
基本身份驗證內置于 HTTP 規范中,它允許客戶端通過發送以特定格式編碼的用戶名和密碼來向服務器驗證自身。
使用基本身份驗證的典型流程如下所示。
其基本思路是:用戶每次向資源服務器發送請求時,都會以特定方式攜帶用戶名和密碼。服務器接收到請求后,會先對這些憑據進行解碼,然后驗證其有效性,最終決定是否允許客戶端訪問相應的資源。
這是一個非常簡單的解決方案,但應謹慎使用,因為它存在一些嚴重的安全風險。Base64編碼僅僅是一種簡單的編碼方式,它可以很容易地被解碼,這意味著通過Base64編碼傳輸的憑據實際上是以一種近乎純文本的形式被發送的。
如果您決定使用此方法,請始終確保使用 HTTPS 通過網絡加密憑據。
1. 我們首先創建一個身份驗證處理程序
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.Fail("Missing Authorization Header");
}
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
// Validate the username and password (this is just an example, you should validate against a user store)
if (username == "admin" && password == "password")
{
var claims = new[] {
new Claim(ClaimTypes.Name, username)
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
else
{
return AuthenticateResult.Fail("Invalid Username or Password");
}
}
catch
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
}
}
這種方法相當直接明了。此處理程序將嘗試提取 Authorization 標頭。如果未設置 authorization 標頭,則拒絕訪問。然后,我們嘗試從標頭中獲取 base64 字符串,并提取用戶名和密碼。同樣,我們只需根據硬編碼值驗證憑證以進行說明。通常,您會針對某些用戶存儲進行檢查。
如果所有步驟都順利完成,我們將構建一個包含必要信息的簡單聲明,并標志著身份驗證的成功完成。
2. 在 Program.cs 中注冊處理程序
using BasicAuth.Handlers;
using Microsoft.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
在這里,我們將應用程序配置為使用基本身份驗證方案,并指定我們的處理程序。
3. 我們現在可以保護我們的端點
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
[Authorize]
public IActionResult Get()
{
return Ok("Protected resource data");
}
}
如上所述,基本身份驗證提供了一種非常簡單的方法,用于對應用程序的請求進行身份驗證。但是,由于其明顯的缺點,此方法絕不應用于促進生產環境中的實際訪問控制。
在過去,我曾在安全性和可伸縮性不是首要考慮因素的簡單場景中,使用基本身份驗證來提供對測試環境的訪問。雖然基本身份驗證快速且易于實施,但它的使用應當十分謹慎,并且僅當確實適合特定情況時才采用。在決定使用之前,請務必考慮其他身份驗證方法是否更能滿足您的具體需求。
API 密鑰授權是一種簡單且廣泛使用的方法,用于控制對 API 的訪問。這種方法非常適合于需要授予對一個或多個終端節點的訪問權限,但無需對用戶的具體身份進行細致管理的場景。當您需要訪問受限 API 時(通常是在注冊 API 之后),通常會采用此方法。注冊后,通常會向客戶端頒發一個 API 密鑰,然后可以存儲該密鑰并用于訪問特定資源。頒發實體具備根據實際需求撤銷API密鑰并使其失效的能力。
從本質上來說,API密鑰是一個分配給客戶端的唯一標識符。客戶端會在其API請求中包含這個密鑰,以此獲取對服務的訪問權限。通常,API密鑰會以x-api-key
的形式作為請求頭發送到服務器,服務器則會通過自定義的邏輯來提取并驗證這個密鑰。
API密鑰的優勢在于為開發人員提供了在實施授權邏輯時的極大靈活性。一般情況下,客戶端的信息會與它們各自的API密鑰以及其他相關的信息一同存儲在數據庫中。例如,您可以存儲客戶端的 IP 地址并驗證請求是否來自預期的客戶端。或者,您也可以在一段時間后使 API 密鑰失效。
關鍵在于,開發人員可以自由地配置所有與授權相關的業務邏輯,這些邏輯可以根據實際需求既復雜又精細,也可以相對簡單直接。這使得 API 密鑰授權成為許多需要簡單性和靈活性的應用程序的不錯選擇,而不是更復雜的授權方法。
典型的 API 密鑰授權流程如下所示:
如上所述,服務器驗證步驟可以根據開發人員的選擇來實現。
在API請求的處理流程中,客戶端發送的API密鑰通常會被一個中間件所捕獲。這個中間件會負責攔截API調用,并在此基礎上執行相應的授權邏輯。
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private const string API_KEY_HEADER_NAME = "X-Api-Key";
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.Request.Headers.TryGetValue(API_KEY_HEADER_NAME, out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("API Key is missing.");
return;
}
var apiKey = "SuperSecret"; // Will normally come from a store/configuration/database
if (!apiKey.Equals(extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized client.");
return;
}
await _next(context);
}
}
在這里,我們首先注入 RequestDelegate,如果一切順利,我們使用它來在 HTTP 請求管道中繼續發送請求,或者在授權失敗時終止請求。
然后,我們嘗試從相應的標頭中提取 API 密鑰,并根據預期的 API 密鑰對其進行檢查。同樣地,API密鑰的存儲通常需要采取某種形式的安全措施,然而在這個簡單的示例中,為了演示目的,我們僅對其進行了硬編碼處理。
如果鍵不匹配,則返回 401。如果一切正常,我們調用 _next 將請求發送到請求管道中的下一個中間件。
2. 在 Program.cs 中注冊中間件
using APIKey.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseMiddleware<ApiKeyMiddleware>();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting();
app.MapControllers();
app.Run();
就這樣。對于 API 收到的每個請求,中間件將提取 API 密鑰并確保其有效,然后允許請求繼續沿請求管道進行。
正如前面所提到的,當我們需要為某個客戶端頒發密鑰,同時又不需要特別精細的身份管理時,采用API密鑰授權無疑是一個很好的選擇。這適用于機器對機器通信,并且可以擴展以適應特定的用例。在上面的示例中,我們研究了最簡單的解決方案,但授權機制可以根據您的需要進行配置。與基本身份驗證相比較,API密鑰授權的一個顯著優勢在于我們無需將敏感憑證直接存儲在實際使用的令牌(即API密鑰)中,這一特性使得它成為了一種更為安全的授權處理方式。
在本文中,我們研究了在 .NET API 中處理授權的四種常用方法,每種方法都有自己的優點和缺點。我們討論了 JWT(JSON Web 令牌)、OAuth、基本身份驗證和 API 密鑰授權。深入理解這些方法對于選擇出最符合特定應用需求的方法至關重要。
授權機制可能會讓人感到難以捉摸,但本文旨在為您提供一個清晰易懂、易于入門的起點。
原文鏈接:https://medium.com/@aschultzme/4-authorization-methods-for-securing-your-net-api-0075f2cf755b?source=search_post