JWT

1、JWT定义

JWT(Json Web Token)是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准( RFC 7519 ),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

1.1、JWT结构

JWT由三部分组成,这些部分由点(.)分隔,分别是:Header(头部包含算法信息) .Payload(负载包含实际传递信息).Signature(对前面两部分的签名)

例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

 

1.2、JWT与传统Session登陆认真对比

1、传统的Cookie-Session认证

2、Cookie-Session认证升级版----redis实现session共享

3、JWT认证流程

使用JWT的优势:

  1. 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  2. 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  3. 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
  4. 不需要在服务端保存会话信息,特别适用于分布式微服务

 

2、JWT使用

生成JWT
 public static string CreateNewJwt()
{
    var claims = new List<Claim>();
    //添加负载
    claims.Add(new Claim(ClaimTypes.NameIdentifier, "6"));
    claims.Add(new Claim(ClaimTypes.Name, "Panda"));
    claims.Add(new Claim(ClaimTypes.Role, "User"));
    claims.Add(new Claim(ClaimTypes.Role, "Manager"));
    claims.Add(new Claim(ClaimTypes.Role, "Admin"));
    claims.Add(new Claim("SomeCode", "Panda666com"));
    //密钥
    string key = "dkdls()dlfj%sldfj#sdlfnbmlkepkkqaopxd@9094";
    //设置过期时间
    DateTime expires = DateTime.Now.AddDays(1);

    byte[] secBytes = Encoding.UTF8.GetBytes(key);
    var secKey = new SymmetricSecurityKey(secBytes);
    var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
    var tokenDescriptor = new JwtSecurityToken(claims: claims,
        expires: expires, signingCredentials: credentials);
    //生成jwt字符串
    string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    return jwt;
}
解码JWT
 //不使用私钥,直接解码
public static string DecodeJwt(string jwtString)
{
    string jwt = jwtString;
    string[] segments = jwt.Split('.');
    string head = JwtDecode(segments[0]);
    string payload = JwtDecode(segments[1]);
    Console.WriteLine("--------head--------");
    Console.WriteLine(head);
    Console.WriteLine("--------payload--------");
    Console.WriteLine(payload);
    string JwtDecode(string s)
    {
        s = s.Replace('-', '+').Replace('_', '/');
        switch (s.Length % 4)
        {
            case 2:
                s += "==";
                break;
            case 3:
                s += "=";
                break;
        }
        var bytes = Convert.FromBase64String(s);
        return Encoding.UTF8.GetString(bytes);
    }

    return "";
}

 由此可见,JWT中payload是明文保存的,不要把不能被客户端知道的信息放到JWT中。

私钥校验JWT并解码
/// <summary>
/// 验证Jwt字符串
/// </summary>
/// <param name="jwtString"></param>
public static Dictionary<string, string> ValidJwt(string jwtString)
{
    Console.WriteLine("校验JWT");
    string secKey = "dkdls()dlfj%sldfj#sdlfnbmlkepkkqaopxd@9094";
    JwtSecurityTokenHandler tokenHandler = new();
    TokenValidationParameters valParam = new();
    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secKey));
    valParam.IssuerSigningKey = securityKey;
    valParam.ValidateIssuer = false;
    valParam.ValidateAudience = false;

    //返回值
    Dictionary<string, string> result = new Dictionary<string, string>();

    try
    {
        //解析Jwt
        ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtString,
            valParam, out SecurityToken secToken);

        foreach (var claim in claimsPrincipal.Claims)
        {
            result[claim.Type] = claim.Value;
            Console.WriteLine($"{claim.Type}={claim.Value}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("failed");
    }
    finally
    {

    }
    return result;
}

测试:

public static void Main(string[] args)
{
    //创建新的Jwt
    string jwtEncodeString = CreateNewJwt();
    Console.WriteLine(jwtEncodeString);

    //读取Jwt
    string jwtDecodeString = DecodeJwt(jwtEncodeString);

    //验证Jwt
    Dictionary<string, string> result = ValidJwt(jwtEncodeString);  
}
结果
 eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjYiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUGFuZGEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVXNlciIsIk1hbmFnZXIiLCJBZG1pbiJdLCJTb21lQ29kZSI6IlBhbmRhNjY2Y29tIiwiZXhwIjoxNzExMDEwNzE4fQ.qpJWz8aEefmUycBYb1qlQz5GBuU5wARNJ-FHvHF6abA
--------head--------
{"alg":"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256","typ":"JWT"}
--------payload--------
{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"6","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"Panda","http://schemas.microsoft.com/ws/2008/06/identity/claims/role":["User","Manager","Admin"],"SomeCode":"Panda666com","exp":1711010718}
校验JWT
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier=6
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name=Panda
http://schemas.microsoft.com/ws/2008/06/identity/claims/role=User
http://schemas.microsoft.com/ws/2008/06/identity/claims/role=Manager
http://schemas.microsoft.com/ws/2008/06/identity/claims/role=Admin
SomeCode=Panda666com
exp=1711010718

注意:

  • 生成jwt时候的key必须在16位以上,否则会因为长度不够抛出异常。
  • jwt本身是不加密的,里面包含的信息任何人都可以读取到。
  • jwt的签名部分是对前两部分的签名,防止数据被篡改。

 

3、.Net Core JWT 授权

1、Nuget 安装 Microsoft.AspNetCore.Authentication.JwtBearer

2、在appsettings.json中配置JWT 参数,如下:

"JwtConfig": {
  "SecretKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密钥   可以是guid 也可以是随便一个字符串
  "Issuer": "zhangsan", // 颁发者
  "Audience": "zhangsan", // 接收者
  "Expired": 30 // 过期时间(30min)
}
JwtConfig
 /// <summary>
/// jwt配置
/// </summary>
public class JwtConfig : IOptions<JwtConfig>
{
    public JwtConfig Value => this;
    public string SecretKey { get; set; }
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public int Expired { get; set; }
    public DateTime NotBefore => DateTime.UtcNow;
    public DateTime IssuedAt => DateTime.UtcNow;
    public DateTime Expiration => IssuedAt.AddMinutes(Expired);
    private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
    public SigningCredentials SigningCredentials =>
        new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
}

3、实体类

LoginUserModel
public class LoginUserModel
{
    public string userid { get; set; }
    public string username { get; set; }
    public string phone { get; set; }
    public string roles { get; set; }
}
JwtTokenResult
 public class JwtTokenResult
{
    public string access_token { get; set; }
    public string refresh_token { get; set; }
    /// <summary>
    /// 过期时间(单位秒)
    /// </summary>
    public int expires_in { get; set; }
    public string token_type { get; set; }
    public LoginUserModel user { get; set; }
}

 4、创建JWT服务类

public class GenerateJwt
{
    private readonly JwtConfig _jwtConfig;
    public GenerateJwt(IOptions<JwtConfig> jwtConfig)
    {
        _jwtConfig = jwtConfig.Value;
    }
    /// <summary>
    /// 生成token
    /// </summary>
    /// <param name="sub"></param>
    /// <param name="customClaims">携带的用户信息</param>
    /// <returns></returns>
    public string GenerateEncodedToken(LoginUserModel customClaims)
    {
        //创建用户身份标识,可按需要添加更多信息
        var claims = new List<Claim>();
        claims.Add(new Claim("phone",customClaims.phone));
        claims.Add(new Claim(ClaimTypes.NameIdentifier, customClaims.userid));
        claims.Add(new Claim(ClaimTypes.Name, customClaims.username));
        foreach (var role in customClaims.roles.Split(','))
        {
            claims.Add(new Claim(ClaimTypes.Role, role));
        }
        //创建令牌
        var jwt = new JwtSecurityToken(
            issuer: _jwtConfig.Issuer,
            audience: _jwtConfig.Audience,
            claims: claims,
            notBefore: _jwtConfig.NotBefore,
            expires: _jwtConfig.Expiration,
            signingCredentials: _jwtConfig.SigningCredentials);
        //生成JWT
        string access_token = new JwtSecurityTokenHandler().WriteToken(jwt);
        //return new JwtTokenResult()
        //{
        //    access_token = access_token,
        //    expires_in = _jwtConfig.Expired * 60,
        //    token_type = JwtBearerDefaults.AuthenticationScheme,
        //    user = customClaims
        //};
        return access_token;
    }
}

5、控制器代码

[Route("api/[controller]/[action]")]
[ApiController]
[Authorize()]//鉴权
public class JwtController : ControllerBase
{
    private readonly GenerateJwt _generateJwt;
    public JwtController(GenerateJwt generate)
    {
        this._generateJwt = generate;
    }

    [AllowAnonymous]//可匿名
    [HttpPost]
    public ActionResult Login([FromBody] LoginUserModel loginUser)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest("Invalid Request");
        }

        var refreshToken = Guid.NewGuid().ToString();
        /*
		生成token之前要验证一下账户是否存在
		*/
        try
        {
            var jwtTokenResult = _generateJwt.GenerateEncodedToken(loginUser);
            ////客户端每次登陆都刷新jwt guid值,与保存的guid相比较
            ////若不相同,则说明是不同客户端登陆,之前登陆的客户端jwt过时,需重新登陆
            //jwtTokenResult.refresh_token = refreshToken;
            //这里可按需返回
            return Ok(jwtTokenResult);
        }
        catch (Exception e) { return BadRequest("Invalid Request"); }
    }

    [Authorize(Roles = "admin")]//角色,可叠加
    [HttpGet]
    public ActionResult GetAuthorization()
    {
        var claims = new LoginUserModel()
        {
            username = User.FindFirst(ClaimTypes.Name).Value,
            userid = User.FindFirst(ClaimTypes.NameIdentifier).Value,
            roles = string.Join(',', User.FindAll(ClaimTypes.Role).Select(c => c.Value)),
            phone = User.FindFirst("phone").Value,
        };
        return Ok(claims);
    }

    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

6、在Startup.cs 中启用Jwt认证

//注入jwt服务
services.AddScoped<GenerateJwt>();
//读取配置文件中的JwtConfig字段
services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));

//将字段中的值赋值给 JwtConfig 实体类
var jwtConfig = new JwtConfig();
Configuration.Bind("JwtConfig", jwtConfig);
//var jwtConfig = Configuration.GetSection("JwtConfig").Get<JwtConfig>();

#region jwt认证
services
    .AddAuthentication(option =>
    {
        //认证middleware配置
        option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            //是否验证SecurityKey
            ValidateIssuerSigningKey = true,
            //Token颁发机构
            ValidIssuer = jwtConfig.Issuer,
            //颁发给谁
            ValidAudience = jwtConfig.Audience,
            //这里的key要进行加密
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecretKey)),
            //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
            ValidateLifetime = true,
            ////过期时间容错值,解决服务器端时间不同步问题(秒)
            //ClockSkew = TimeSpan.FromSeconds(30),
            //RequireExpirationTime = true,
        };
    });
#endregion

启用认证、授权中间件:

app.UseAuthentication();
app.UseAuthorization();

7、配置Swagger全局授权

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
    #region 启用swagger验证功能
    //添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称一致即可。
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格",
        Name = "Authorization",//jwt默认的参数名称
        In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
        Type = SecuritySchemeType.ApiKey,
        BearerFormat = "JWT",
        Scheme = "Bearer",

    });
    #endregion
});

Authotrize授权,Value:Bearer {jwt字符串},注意:中间加一个空格

8、测试

Login接口:

入参:出参:

Authotrize授权后,GetAuthorization接口:

get接口未授权:

posted @   茜茜87  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
历史上的今天:
2023-03-21 SqlServer基础知识
点击右上角即可分享
微信分享提示