保护您的 .NET 应用程序:最好的 4 种授权机制

作者:微信公众号:【架构师老卢】
10-4 8:42
130

在本文中,我们将探讨在 .NET API 中实现授权的四种常见方法:JWT(JSON Web 令牌)、OAuth基本身份验证API 密钥授权。每种方法都有其优点和缺点,选择使用哪种方法取决于您的具体应用要求。授权对于保护 API 和确保只有经过身份验证和授权的用户才能访问特定资源至关重要。

1. JWT(JSON Web 令牌)

JSON Web 令牌 (JWT) 是一种紧凑、URL 安全的方式,用于表示要在两方之间传输的声明。JWT 通常用于 Web 应用程序中的授权,允许服务器对用户进行身份验证并向他们颁发可用于后续请求的令牌。

令牌是一个字符串,由三部分组成:HeaderPayloadSignature,并且它是自包含的,这意味着所有必要的信息(声明)都在令牌本身中编码。

用法

JWT 是现代 Web 应用程序中基于令牌的无状态身份验证的绝佳选择。它们是独立的,允许跨分布式系统进行灵活的授权。但是,由于它们不可撤销,因此您必须确保令牌的生存期较短且可刷新。

JWT 的工作原理

典型的 JWT 身份验证流程包括:

  1. 客户端身份验证
    用户提供凭据 (用户名和密码)。
  2. 令牌颁发
    服务器验证凭据并颁发 JWT,其中包括有关用户和任何相关声明的信息。
  3. 客户端存储
    客户端存储令牌(通常在 localStorage 或 sessionStorage 中),并将其包含在每个后续请求的 Authorization 标头(Bearer 令牌)中。
  4. 服务器验证
    对于每个请求,服务器都会验证 JWT 以确保它未过期、具有正确的签名并包含有效的声明。

在 .NET Core 中实现 JWT

第 1 步:安装所需的软件包
首先,我们需要安装一些处理 JWT 身份验证的 NuGet 包。
Package Manager 控制台中运行以下命令:

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer  
Install-Package Microsoft.IdentityModel.Tokens  
Install-Package Microsoft.IdentityModel.JsonWebTokens

步骤 2:在 我们现在需要在文件中设置 JWT 身份验证。Program.cs
Program.cs

  • 定义用于对令牌进行签名的密钥(此密钥应安全地存储在实际应用程序中,例如环境变量中)。
  • 设置身份验证服务以使用 JWT Bearer 令牌。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
//Define a secret key (ensure it's securely stored in production)
var key = "This is my custom Secret key for authentication";

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    // Configure token validation parameters
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true, // Token should expire at a specific time
        ValidateIssuerSigningKey = true, // Validate the signature
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) // Use the secret key to validate the token
    };
});

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

//Enable authentication and authorization in the middleware pipeline
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

第 3 步:创建登录端点以生成 JWT 令牌
接下来,我们将创建一个身份验证控制器。此控制器将验证用户的凭据,并在成功登录后返回 JWT 令牌。

using 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";
        }

        // POST /api/auth/login
        [HttpPost("login")]
        public IActionResult Login([FromBody] UserLogin userLogin)
        {
            // Simple username/password check (in production, check against a database)
            if (userLogin.Username == "test" && userLogin.Password == "password")
            {
                // Generate a JWT token for the user
                var token = GenerateJwtToken(userLogin.Username);
                return Ok(new { token });
            }

            // Return Unauthorized if credentials are wrong
            return Unauthorized();
        }

        // Method to generate a JWT token
        private string GenerateJwtToken(string username)
        {
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, username), // Subject claim (the username)
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Unique token ID
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_key)); // Create key from the secret
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // Sign the token using HMAC SHA256

            // Create the token
            var token = new JwtSecurityToken(
                issuer: null, // Not using issuer validation
                audience: null, // Not using audience validation
                claims: claims,
                expires: DateTime.Now.AddMinutes(30), // Token expiration time
                signingCredentials: creds // Include signing credentials
            );

            return new JwtSecurityTokenHandler().WriteToken(token); // Return the token as a string
        }
    }

    // Model for accepting login requests
    public class UserLogin
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}
  • 它包含一个 POST 端点,用于检查用户名和密码(在实际应用程序中,您应该根据数据库进行检查)。AuthController/login
  • 如果凭证有效,则会生成并返回 JWT 令牌。
  • 该方法创建一个包含用户信息(在本例中为用户名)的令牌,并设置过期时间(30 分钟)。GenerateJwtToken

步骤 4:使用授权
保护终端节点设置 JWT 身份验证后,我们可以通过要求有效的令牌来访问 API 端点,从而保护它们。这是通过将属性添加到控制器或特定操作来完成的。[Authorize]

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    [Authorize] // This endpoint requires a valid token
    public IActionResult Get()
    {
        return Ok(new { message = "This is a protected endpoint" });
    }
}
  • 该属性确保只有具有有效 JWT 令牌的经过身份验证的用户才能访问此终端节点。[Authorize]
  • 如果请求标头中未提供有效令牌,则 API 将返回响应。401 Unauthorized

通过执行这些步骤,您可以在 .NET API 中实现基于 JWT 的授权。JWT 允许安全、无状态的身份验证,并广泛用于现代应用程序。使用该属性,您可以轻松保护 API 的特定部分,确保只有经过身份验证的用户才能访问。[Authorize]

2. OAuth(开放授权)

OAuth 是一个授权框架,它允许第三方服务在不暴露用户凭据的情况下交换令牌。OAuth 广泛用于保护 API,尤其是在允许用户使用 Google 或 Facebook 等第三方服务登录时。

用法

在构建将与第三方服务交互的 API 或您需要允许用户使用外部提供商登录时,OAuth 是理想的选择。它对于用户委托的授权和单点登录 (SSO) 方案特别有用。与基本身份验证等传统方法相比,OAuth 提供了更高级别的安全性和灵活性。

OAuth 的工作原理

OAuth 使用访问令牌授予对 API 的有限访问权限,而无需用户与应用程序共享其密码。一般流程包括:

  1. 授权请求:客户端向用户请求授权。
  2. Token Exchange:客户端将授权码交换为来自授权服务器的 Access Token。
  3. 资源访问:客户端使用访问令牌访问受保护的资源。

OAuth 允许用户登录第三方应用程序,而无需创建单独的凭据。相反,用户通过使用 Google、GitHub 或 Facebook 等服务提供对其现有账户的访问权限来委派对这些应用程序的权限。

在 .NET Core 中实现 OAuth

步骤 1:安装所需的 NuGet 包
首先,我们需要安装必要的软件包,以帮助我们处理 JWT 令牌和身份验证。

打开 Package Manager 控制台并运行

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer  
Install-Package Microsoft.IdentityModel.Tokens  
Install-Package Microsoft.IdentityModel.JsonWebTokens

步骤 2:在 在您的文件中,我们需要配置身份验证服务以接受 JWT 令牌。以下代码设置身份验证,指定 JWT 作为默认方案,并配置 Token 验证参数,例如检查颁发者、受众和签名密钥。Program.cs
Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;  
using Microsoft.IdentityModel.Tokens;  
using System.Text;  
  
var builder = WebApplication.CreateBuilder(args);  
  
// Secret key (Store securely in environment variables or a secret manager)  
var key = "This is my custom Secret key for authentication";  
  
// Configure Authentication with JWT  
builder.Services.AddAuthentication(options =>  
    {  
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;  
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;  
    })  
    .AddJwtBearer(options =>  
    {  
        options.TokenValidationParameters = new TokenValidationParameters  
        {  
            ValidateIssuer = true, // Ensures token was issued by the expected server  
            ValidateAudience = true, // Ensures token is intended for the correct audience  
            ValidateIssuerSigningKey = true, // Ensures token signature is correct  
            ValidIssuer = "https://localhost:7232", // Token issuer  
            ValidAudience = "https://localhost:7232", // Token audience  
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) // Signature key  
        };  
    });  
  
builder.Services.AddAuthorization(); // Enables authorization based on roles/claims  
builder.Services.AddControllers(); // Adds support for controllers and APIs  
builder.Services.AddSwaggerGen(); // For Swagger API documentation  
  
var app = builder.Build();  
  
// Configure HTTP request pipeline  
if (app.Environment.IsDevelopment())  
{  
    app.UseSwagger();  
    app.UseSwaggerUI();  
}  
  
app.UseRouting();  
app.UseAuthentication(); // Ensures JWT authentication middleware is invoked  
app.UseAuthorization();  // Ensures authorization policies are applied  
  
app.MapControllers();  
app.Run();

步骤 3:创建 Token 终端节点
接下来,我们将创建一个控制器,该控制器将充当我们的授权服务器。它将验证客户端凭证并颁发 JWT 令牌。

创建一个名为TokenController.cs

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;

namespace MyApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TokenController : ControllerBase
    {
        private const string ClientId = "1"; // Static client_id (In production, retrieve from DB)
        private const string ClientSecret = "secret"; // Static client_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)
        {
            // Validate client credentials
            if (client_id == ClientId && client_secret == ClientSecret)
            {
                // Create token
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(Key);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new[] { new Claim("sub", client_id) }), // Token payload
                    Expires = DateTime.UtcNow.AddHours(1), // Token expiration time (1 hour)
                    Issuer = Issuer,
                    Audience = Audience,
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                };
                
                var token = tokenHandler.CreateToken(tokenDescriptor);
                var tokenString = tokenHandler.WriteToken(token); // Convert token to string

                return Ok(new
                {
                    access_token = tokenString, // Token to be used for future requests
                    token_type = "Bearer", // OAuth2 token type
                    expires_in = 3600 // Expiration time in seconds (1 hour)
                });
            }

            return BadRequest("Invalid client credentials");
        }
    }
}
  • 客户端凭证:为简单起见,我们对 和 .在生产环境中,这些应该安全地存储和从数据库中检索。client_idclient_secret

客户端可以使用此终端节点返回的令牌来访问受保护的资源。

步骤 4:使用 Token
保护资源现在我们已经实现了令牌生成,我们可以使用 JWT 令牌保护 API 终端节点。通过添加该属性,我们确保只有具有有效令牌的请求才能访问此终端节点。[Authorize]

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

namespace MyApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ResourceController : ControllerBase
    {
        [HttpGet]
        [Authorize] // Protects this endpoint with JWT authorization
        public IActionResult Get()
        {
            return Ok("Protected resource data"); // Only accessible with valid JWT
        }
    }
}

第 5 步:测试 OAuth 2.0 流程
使用 Postman 或 Curl 等工具将请求发送到带有 和 的终端节点。POST/tokenclient_idclient_secret

请求示例:

curl -X POST https://localhost:7232/api/token \
    -F "client_id=1" \
    -F "client_secret=secret"

这将返回一个访问令牌:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token_type": "Bearer",
    "expires_in": 3600
}

使用返回的令牌访问终端节点。/resource

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
     https://localhost:7232/api/resource

如果令牌有效,您将收到包含受保护数据的响应

"Protected resource data"

此简化示例演示如何在 .NET API 中实现 OAuth 2.0 和 JWT 身份验证。通过使用令牌保护终端节点,我们确保只有授权用户或客户端才能访问敏感数据或功能。在生产系统中,请务必安全地存储密钥,实施适当的错误处理,并配置令牌过期和刷新机制,以实现最佳安全性和性能。

3. 基本身份验证

基本身份验证是一种更简单、安全性较低的方法,用于对 Web 资源实施访问控制。它内置于 HTTP 规范中,允许客户端通过发送以 base64 编码的用户名和密码来向服务器进行身份验证。

用法

基本身份验证提供了一种非常简单的方法来保护 API,但只应在低安全性情况下使用。由于缺乏安全性和可扩展性,此方法不适用于生产环境。

基本身份验证的工作原理

  1. 客户端请求:客户端发送一个 HTTP 请求,其标头包含以 base64 编码的凭据。Authorization
  2. 服务器验证:服务器对凭证进行解码并验证它们。
  3. Access Granted/Denied:如果凭证有效,则服务器将授予对所请求资源的访问权限。

基本身份验证应仅与 HTTPS 一起使用,以确保凭据在传输过程中加密。

在 .NET Core 中实现基本身份验证

步骤 1:创建身份验证处理程序
首先,我们需要创建自定义身份验证处理程序。此处理程序将负责验证 HTTP 标头中发送的凭据。Authorization

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;

// Custom Basic Authentication Handler
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()
    {
        // Check if the Authorization header is present
        if (!Request.Headers.ContainsKey("Authorization"))
        {
            return AuthenticateResult.Fail("Missing Authorization Header");
        }

        try
        {
            // Parse the Authorization header
            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 should be done against a user store)
            if (username == "admin" && password == "password")
            {
                // Create claims and principal for the authenticated user
                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");
        }
    }
}

第 2 步:注册身份验证处理程序
接下来,我们需要在应用程序的文件中注册我们的自定义身份验证处理程序。Program.cs

using Microsoft.AspNetCore.Authentication; // For authentication services

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers(); 

// Configure BasicAuthentication
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(); // Enable authentication
app.UseAuthorization(); // Enable authorization

app.MapControllers();

app.Run();

第 3 步:保护 API 终端节点
最后,我们可以使用 attribute 保护我们的 API 端点。下面是一个返回天气预报的控制器示例[Authorize]

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    // Secure the endpoint with [Authorize] attribute
    [HttpGet]
    [Authorize]
    public IActionResult Get()
    {
        return Ok("Protected resource data");
    }
}

此示例说明了如何在 .NET 应用程序中实现基本身份验证。虽然它对于测试来说既简单又有效,但在生产中使用它时要谨慎。始终优先考虑凭据的安全存储和传输,并根据应用程序的要求选择合适的身份验证方法。

4. API Key 授权

API 密钥授权是一种简单的方法,通过向每个客户端颁发唯一密钥来控制对 API 的访问。客户端在其 API 请求中包含此密钥,服务器验证密钥以授予或拒绝访问权限。

用法

API 密钥授权非常适合需要一种简单、低开销的方法来保护对 API 的访问的方案。它通常用于机器对机器通信或与外部服务集成时。

API 密钥授权的工作原理

  1. API 密钥生成:服务器为每个客户端生成一个唯一的密钥。
  2. 客户端请求:客户端在请求标头中包含密钥。
  3. Server Verification(服务器验证):服务器根据存储的有效密钥验证密钥。
  4. Access Granted/Denied:如果密钥有效,则请求将继续。

在 .NET Core 中实现 API 密钥授权

第 1 步:创建 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)
    {
        // Check if the request contains the API key in the headers
        if (!context.Request.Headers.TryGetValue(API_KEY_HEADER_NAME, out var extractedApiKey))
        {
            context.Response.StatusCode = 401; // Unauthorized
            await context.Response.WriteAsync("API Key is missing.");
            return;
        }

        // Define the correct API key (this should be stored securely)
        var correctApiKey = "SuperSecret"; 

        // Compare the extracted key with the correct one
        if (!correctApiKey.Equals(extractedApiKey))
        {
            context.Response.StatusCode = 401; // Unauthorized
            await context.Response.WriteAsync("Unauthorized client.");
            return;
        }

        // Call the next middleware in the pipeline
        await _next(context);
    }
}

第 2 步:注册 Middleware
要在应用程序中使用中间件,您需要在 中注册它。Program.cs

using APIKey.Middleware; // Assuming the middleware is inside this namespace  
  
var builder = WebApplication.CreateBuilder(args);  
  
builder.Services.AddControllers(); // Add controller support  
builder.Services.AddEndpointsApiExplorer();  
builder.Services.AddSwaggerGen(); // Enable Swagger for API documentation  
  
var app = builder.Build();  
  
// Register the API Key Middleware  
app.UseMiddleware<ApiKeyMiddleware>();  
  
// Enable Swagger for development  
if (app.Environment.IsDevelopment())  
{  
    app.UseSwagger();  
    app.UseSwaggerUI();  
}  
  
app.UseHttpsRedirection();  
app.UseRouting();  
  
app.MapControllers();  
  
app.Run();

第 3 步:保护端点
注册中间件后,您的 API 控制器将自动需要 API 密钥。

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("This is a protected resource, accessible with a valid API key.");
    }
}
  • 对于每个 API 请求,中间件会检查标头是否包含有效的 API 密钥。X-Api-Key
  • 如果 API 密钥有效,则请求将继续向控制器发送并获取资源。
  • 如果 key 无效或缺失,则停止请求并返回未经授权的错误。

此实现演示了一个基本且易于理解的 API 密钥授权系统。在生产设置中,API 密钥通常存储在更安全的位置,例如配置文件、数据库或环境变量,而不是硬编码。

四种授权方法(JWTOAuth基本身份验证API 密钥授权)中的每一种都有自己的优势和用例。虽然 JWT 和 OAuth 为现代应用程序提供了更强大、更安全的方法,但基本身份验证和 API 密钥可用于更简单的使用案例。了解每种方法的优点和局限性将有助于您根据特定应用程序需求做出最佳决策。

相关留言评论
昵称:
邮箱:
阅读排行