JWT的简单使用(菜鸡随笔)

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鉴权流程

  1. 用户登录,向服务器提交身份凭证(例如用户名、密码)。
  2. 服务器验证身份凭证的有效性。
  3. 如果身份凭证有效,服务器生成一个JWT并将其返回给客户端。
  4. 客户端在以后的请求中携带JWT。
  5. 服务器从JWT中解析出用户ID等信息,并根据信息来验证用户身份和授权信息。

JWT鉴权实现

配置appsettings.json

我们需要在appsettings.json文件中配置JWT的相关信息。在您的ASP.NET Core项目中,找到appsettings.json文件,并添加以下配置:

{
  "JwtConfig": {
    "Issuer": "yourIssuer",
    "Audience": "yourAudience",
    "SecretKey": "yourSecretKey",
    "AccessTokenExpirationMinutes": 60,
    "RefreshTokenExpirationMinutes": 1440
  }
}
创建JWT基类
csharpCopy codepublic class JwtSettings
{
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public string SecretKey { get; set; }
    public int AccessTokenExpirationMinutes { get; set; }
    public int RefreshTokenExpirationMinutes { get; set; }
}
添加 JWT 鉴权服务

在ASP.NET Core中,可以使用JwtBearer认证方案来验证JWT。首先,在Startup.cs文件中添加以下代码:

public void ConfigureServices(IServiceCollection services)
{
    // 添加JWT身份验证服务
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        var config = Configuration.GetSection("JwtConfig").Get<JwtConfig>(); // 从appsettings.json读取JwtConfig配置
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = config.Issuer,
            ValidAudience = config.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey))
        };
        
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = async context =>
            {
                var token = context.Request.Cookies["access_token"]; // 从Cookie中获取token值
                if (!string.IsNullOrEmpty(token))
                {
                    context.Token = token; // 将token值设置到JwtBearer上下文中的Token属性
                }
            }
        };
    });
}
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 claims = new List<Claim>
{
    new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
    new Claim("role", "admin"),
    new Claim(ClaimTypes.NameIdentifier, user.Id),
    new Claim(ClaimTypes.Name, user.Username)
};

var key = Encoding.ASCII.GetBytes(Configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor {
    Issuer = "yourIssuer",
    Audience = "yourAudience",
    Subject = new ClaimsIdentity(claims),
    Expires = DateTime.UtcNow.AddMinutes(30),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};

var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);

在这里,我们首先创建了一个声明(Claims)列表,其中包含用户ID、JWT ID和角色信息。然后,我们指定了JWT的过期时间和签名算法,并使用SymmetricSecurityKey类来指定密钥。最后,我们使用JwtSecurityTokenHandler类将token转换为字符串形式的jwt。

刷新Token

使用GenerateRefreshToken方法生成一个随机的32位Base64编码的字符串作为刷新令牌。

   public string GenerateRefreshToken()
    {
        var randomNumber = new byte[32];
        using (var rng = new RNGCryptoServiceProvider())
        {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }
    }
验证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授权流程

  1. 用户登录,向服务器提交身份凭证(例如用户名、密码)。
  2. 服务器验证身份凭证的有效性。
  3. 如果身份凭证有效,服务器生成一个JWT并将其返回给客户端。
  4. 客户端在以后的请求中携带JWT。
  5. 服务器从JWT中解析出用户信息和声明信息,并根据信息来验证用户是否有权访问特定资源。

JWT授权实现

用户登录

在用户登录时,我们需要对用户提供的用户名和密码进行验证,并生成访问令牌和刷新令牌。下面是一个简单的示例,演示如何在ASP.NET Core中实现用户登录验证,并生成JWT令牌。

[HttpPost("login")]
public IActionResult Login(UserModel model)
{
    // 验证用户名和密码
    var isValidUser = ValidateUser(model.Username, model.Password);

    if (!isValidUser)
    {
        return BadRequest(new { message = "Invalid username or password" });
    }

    // 生成访问令牌
    var accessToken = _jwtService.GenerateAccessToken(model);

    // 生成刷新令牌
    var refreshToken = _jwtService.GenerateRefreshToken();

    // 返回访问令牌和刷新令牌给客户端
    return Ok(new
    {
        access_token = accessToken,
        refresh_token = refreshToken
    });
}

在上面的示例中,我们通过调用_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方法来生成新的访问令牌,并将其返回给客户端。在生成新的访问令牌时,我们可以使用之前保存的用户信息,例如用户名等。

同时,在ASP.NET Core中,可以使用Policy-Based授权方案来实现JWT授权。首先,在Startup.cs文件中添加以下代码:

services.AddAuthorization(options => {
    options.AddPolicy("AdminOnly", policy => policy.RequireClaim("role","admin"));
});

在上面的代码中,我们使用AddPolicy方法添加一个名为“AdminOnly”的策略,该策略要求JWT中包含一个名为“role”且值为“admin”的声明。

在需要进行授权的API端点或控制器上,使用Authorize属性来应用策略。例如:

[HttpGet("my-action")]
[Authorize(Policy = "AdminOnly")]
public IActionResult MyAction() {
    return Ok("Hello World");
}

这样,只有拥有包含“role”声明且值为“admin”的JWT令牌的用户才能访问MyAction方法。

完整代码

下面是一个包含生成 JWT,解析 JWT,鉴权,授权和策略的完整示例。请注意,此示例仅供参考,请根据实际需求进行修改。

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;

namespace WebApplication1
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            // 添加JWT身份验证服务
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                var config = Configuration.GetSection("JwtConfig").Get<JwtConfig>(); // 从appsettings.json读取JwtConfig配置
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = config.Issuer,
                    ValidAudience = config.Audience,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey))
                };

                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = async context =>
                    {
                        var token = context.Request.Cookies["access_token"]; // 从Cookie中获取token值
                        if (!string.IsNullOrEmpty(token))
                        {
                            context.Token = token; // 将token值设置到JwtBearer上下文中的Token属性
                        }
                    }
                };
            });

            // 添加授权策略
            services.AddAuthorization(options =>
            {
                options.AddPolicy("AdminOnly", policy => policy.RequireRole("admin"));
            });
        }

        public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IWebHostEnvironment env)
        {
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

    public interface IJwtService
    {
        string GenerateAccessToken(User user);
        string GenerateRefreshToken();
        bool ValidateAccessToken(string token);
    }

    public class JwtService : IJwtService
    {
        private readonly JwtConfig _jwtConfig;

        public JwtService(IConfiguration configuration)
        {
            _jwtConfig = configuration.GetSection("JwtConfig").Get<JwtSettings>();
        }

        public string GenerateAccessToken(User user)
        {
            // 设置Token的Claims
            var claims = new List<Claim>
            {
                 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                 new Claim(ClaimTypes.NameIdentifier, user.Id),
                 new Claim(ClaimTypes.Name, user.Username),
                 new Claim("role", "admin")
            };

            // 生成Token的密钥
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings.SecretKey));

            SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Issuer = "yourIssuer",
                Audience = "yourAudience",
                Subject = new ClaimsIdentity(claims),
                // 生成Token的签名证书
                Expires = DateTime.Now.AddMinutes(appSettings.AccessTokenExpirationMinutes),
                // 生成Token的签名证书
                SigningCredentials = creds
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);
            return tokenString;
        }

        public string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
        }

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

    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly IUserService _userService;
        private readonly JwtService _jwtService;

        public UserController(IUserService userService, JwtService jwtService)
        {
            _userService = userService;
            _jwtService = jwtService;
        }

        [HttpPost("api/login")]
        public IActionResult Login(User model)
        {
            var user = _userService.GetUserByUsernameAndPassword(model.Username, model.Password);
            if (user == null)
            {
                return Unauthorized();
            }
            // 生成访问令牌
            var accessToken = _jwtService.GenerateAccessToken(model);

            // 生成刷新令牌
            var refreshToken = _jwtService.GenerateRefreshToken();

            // 返回访问令牌和刷新令牌给客户端
            return Ok(new
            {
                access_token = accessToken,
                refresh_token = refreshToken
            });
        }

        [Authorize]
        [HttpGet("api/user")] 
        public IActionResult GetUser() 
        { 
            var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            var user = _userService.GetUserById(userId); return Ok(user); 
        }
        [Authorize(Policy = "AdminOnly")]
        [HttpDelete("api/user/{userId}")]
        public IActionResult DeleteUser(string userId)
        {
            _userService.DeleteUser(userId);
            return NoContent();
        }

        [HttpPost("refresh")]
        public IActionResult RefreshToken(User model)
        {
            // 验证刷新令牌是否有效
            var isValidRefreshToken = _jwtService.ValidateAccessToken(model.RefreshToken);

            if (!isValidRefreshToken)
            {
                return BadRequest(new { message = "Invalid refresh token" });
            }

            // 生成新的访问令牌
            var accessToken = _jwtService.GenerateAccessToken(model);

            // 返回新的访问令牌给客户端
            return Ok(new
            {
                access_token = accessToken
            });
        }
    }

    public interface IUserService
    {
        User GetUserByUsernameAndPassword(string username, string password);
        User GetUserById(string id);
        void DeleteUser(string id);
    }

    public class UserService : IUserService
    {
        private readonly List<User> _users = new List<User>
    {
        new User { Id = "1", Username = "admin", Password = "password", Role = "admin" },
        new User { Id = "2", Username = "user", Password = "password", Role = "user" },
    };

        public User GetUserByUsernameAndPassword(string username, string password)
        {
            return _users.FirstOrDefault(u => u.Username == username && u.Password == password);
        }

        public User GetUserById(string id)
        {
            return _users.FirstOrDefault(u => u.Id == id);
        }

        public void DeleteUser(string id)
        {
            var user = _users.FirstOrDefault(u => u.Id == id);
            if (user != null)
            {
                _users.Remove(user);
            }
        }
    }

    public class User
    {
        public string Id { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public string Role { get; set; }
        public string RefreshToken { get; set; }
    }
}

注意事项

  • 密钥管理:在使用JWT时,密钥是非常重要的,泄露密钥会导致安全问题。因此,密钥的生成、存储和更新都必须谨慎处理。

  • 过期时间:在生成JWT时,要指定合适的过期时间,避免JWT过期后仍然可以使用。

  • 签名算法:签名算法的选择很重要,不同的签名算法具有不同的安全性和效率。建议采用HMAC+

  • SHA256或RSA算法。

    • 不要存储敏感信息:JWT虽然安全,但仍然存在被盗用的可能性。因此,在生成JWT时,应避免将敏感信息(例如密码、信用卡号等)存储在负载中。
    • 使用HTTPS:在使用JWT时,建议采用HTTPS协议来保证通讯的安全性。
    • 谨慎处理“记住我”功能:在实现“记住我”功能时,需要谨慎处理,避免密钥泄露或用户凭证被盗用。

    结论

    在.NET 5 中使用 JWT 进行授权鉴权是一种安全、可靠的身份验证方式。通过添加 JWT 鉴权服务、使用 Authorize 属性启用 JWT 授权、生成和验证 JWT、使用 UseAuthentication 和 UseAuthorization 中间件来启用身份验证和授权,并为不同的 API 设置不同的授权策略,可以轻松地实现 JWT 的授权鉴权功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值