.Net Core 基于JWT签发Token

 

如果不了解JWT可以先了解这篇文章 。 这里主要是来记录一下怎样使用Jwt 自己来签发和刷新Token,很多地方不符合实际使用,只是为了在这里测试达到效果,正式使用根据实际情况修改代码

1. 添加Nuget引用

1
Microsoft.AspNetCore.Authentication.JwtBeare
1
System.IdentityModel.Tokens.Jwt

2. 添加简单封装的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
public class JwtHelper
    {
        public IConfiguration _configuration { get; set; }
        public JwtHelper(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        /// <summary>
        /// 生成AccessToken
        /// </summary>
        /// <param name="username">这里测试用的是用户信息,可以传入其他信息</param>
        /// <returns></returns>
        public string GenerateAccessToken(string username)
        {
            var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
            string issuer = _configuration.GetSection("JwtConfig:Issuer").Value;
            // 获取SecurityKey
            string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value;
            //------------生成AccessToken----------------------------------
            // token中的claims用于储存自定义信息,如登录之后的用户id等
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub,username),
                new Claim(ClaimTypes.Role,"admin")
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
            //生成Token两种方式
            //方式一
            //var tokenDescriptor = new SecurityTokenDescriptor
            //{
            //    Issuer = issuer,
            //    Audience = "testClient",
            //    NotBefore = DateTime.Now, // 预设值就是 DateTime.Now
            //    IssuedAt = DateTime.Now, // 预设值就是 DateTime.Now
            //    Subject = new ClaimsIdentity(claims),
            //    Expires = DateTime.Now.AddMinutes(30),
            //    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            //};
            //var securityToken = jwtSecurityTokenHandler.CreateToken(tokenDescriptor);
            //var serializeToken = jwtSecurityTokenHandler.WriteToken(securityToken);
            //方式二
            var token = new JwtSecurityToken(
                issuer: issuer,                    // 发布者
                audience: "testClient",                // 接收者
                notBefore: DateTime.Now,                                                          // token签发时间
                expires: DateTime.Now.AddMinutes(30),                                             // token过期时间
                claims: claims,                                                                   // 该token内存储的自定义字段信息
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)    // 用于签发token的秘钥算法
            );
            return jwtSecurityTokenHandler.WriteToken(token);
        }
        /// <summary>
        /// 生成RefreshToken
        /// </summary>
        /// <returns></returns>
        public string GenerateRefreshToken()
        {
            string issuer = _configuration.GetSection("JwtConfig:Issuer").Value;
            // 获取SecurityKey
            string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value;
            var refClaims = new[]
            {
               new Claim("role","refresh")
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
            var refreshToken = new JwtSecurityToken(
                issuer: issuer,                    // 发布者
                audience: "testClient",                // 接收者
                notBefore: DateTime.Now,                                                          // token签发时间
                expires: DateTime.Now.AddDays(7),                                             // token过期时间
                claims: refClaims,                                                                   // 该token内存储的自定义字段信息
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)    // 用于签发token的秘钥算法
            );
 
            // 返回成功信息,写出token
            return new JwtSecurityTokenHandler().WriteToken(refreshToken);
        }
        /// <summary>
        /// 刷新accessToken
        /// </summary>
        /// <param name="accessToken">过期的accessToken</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public string RefreshToken(string accessToken)
        {
            string issuer = _configuration.GetSection("JwtConfig:Issuer").Value;
            // 获取SecurityKey
            string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value;
            var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
            bool isCan = jwtSecurityTokenHandler.CanReadToken(accessToken);//验证Token格式
            if (!isCan)
                throw new Exception("传入访问令牌格式错误");
            //var jwtToken = jwtSecurityTokenHandler.ReadJwtToken(refreshtoken);//转换类型为token,不用这一行
            var validateParameter = new TokenValidationParameters()//验证参数
            {
                ValidateAudience = true,
                // 验证发布者
                ValidateIssuer = true,
                // 验证过期时间
                ValidateLifetime = false,
                // 验证秘钥
                ValidateIssuerSigningKey = true,
                // 读配置Issure
                ValidIssuer = issuer,
                // 读配置Audience
                ValidAudience = "testClient",
                // 设置生成token的秘钥
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey))
            };
 
            //验证传入的过期的AccessToken
            SecurityToken validatedToken = null;
            try
            {
                jwtSecurityTokenHandler.ValidateToken(accessToken, validateParameter, out validatedToken);//微软提供的验证方法。那个out传出的参数,类型是是个抽象类,记得转换
            }
            catch (SecurityTokenException)
            {
                throw new Exception("传入AccessToken被修改");
            }
            // 获取SecurityKey
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
            var jwtToken = validatedToken as JwtSecurityToken;//转换一下
            var accClaims = jwtToken.Claims;
            var access_Token = new JwtSecurityToken(
                    issuer: "fcb",                    // 发布者
                                                      //audience: "myClient",                // 接收者
                    notBefore: DateTime.Now,                                                          // token签发时间
                    expires: DateTime.Now.AddMinutes(30),                                             // token过期时间
                    claims: accClaims,                                                                   // 该token内存储的自定义字段信息
                    signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)    // 用于签发token的秘钥算法
                );
            // 返回成功信息,写出token
            return new JwtSecurityTokenHandler().WriteToken(access_Token);
        }
    }

3.  修改Program.cs 

(这里多设置了swagger,方便测试,如果实际情况不需要swagger进行测试可以去掉 AddSwaggerGen 中参数配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
builder.Services.AddSwaggerGen(c => {
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
    {
        Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer Token",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        BearerFormat = "JWT",
        Scheme = "Bearer"
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });
 
});
builder.Services.AddScoped<JwtHelper>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        // 当验证失败时,表头WWW-Authenticate会返回失败原因
        options.IncludeErrorDetails = true; <br>     //配置Token的验证
        options.TokenValidationParameters = new TokenValidationParameters
        {
            // 可以从 "sub" 取值并设定給 User.Identity.Name
            NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
            // 可以从 "roles" 取值,并可以从 [Authorize] 设置角色
            RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
            // 一般我们都要验证 Issuer
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration.GetValue<string>("JwtConfig:Issuer"),
            // 通常不太需要验证 Audience
            ValidateAudience = false,
            //ValidAudience = "JwtAuthDemo", // 不验证就不需要
            // 一般我们都会验证 Token 的有效期
            ValidateLifetime = true,
            // 如果 Token 中包含 key 才需要验证,一般都只有前面而已
            ValidateIssuerSigningKey = false,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue<string>("JwtConfig:SecurityKey")))
         };
    });

添加认证管道,系统里面已经存在  UseAuthorization  ,这里 在 UseAuthorization   之前添加  UseAuthentication

1
2
app.UseAuthentication();
app.UseAuthorization();

 

4.  添加控制器

这里刷新Token的接口限制了  [Authorize(Roles = "refresh")]  ,只有 refreshToken 才有相应的角色,所以 需要换成  refreshToken ,并且传参之前过期的accessToken,目的主要是拿取token中的claim信息,方便生成新的accessToken重新写入进去, 当前也可以特别处理refreashToken,而取消传入失效的accessToken,我这里没有试过,理论上是可以的。这里还存在一个问题就是可以通过refreshToken 去请求 其他 需要的 accessToken 验证的接口 ,所以可以给相应的接口新增一些限制,只能通过 accessToken 去请求,比如下面的接口 Test2 限制了  [Authorize(Roles = "admin")]   ,accessToken 才有admin的角色权限,只能通过accessToken 去请求 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[Route("api/[controller]")]
[ApiController]
public class AccessController : ControllerBase
{
    private JwtHelper _jwtHelper;
    public AccessController(JwtHelper jwtHelper)
    {
        _jwtHelper = jwtHelper;
    }
    [Authorize]
    [HttpGet("test")]
    public ActionResult Test()
    {
        return Ok(HttpContext.User.Claims.Count());
    }
    [Authorize(Roles = "admin")]
    [HttpGet("test2")]
    public ActionResult Test2()
    {
        return Ok(HttpContext.User.Claims.Count());
    }
    [HttpGet("login")]
    public ActionResult Login(string username, string password)
    {
        //校验账号密码,这里省略
        if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
        {
            var access_token= _jwtHelper.GenerateAccessToken(username);
            var refresh_token = _jwtHelper.GenerateRefreshToken();
            // 返回成功信息,写出token
            return Ok(new { code = 200, message = "登录成功", accessToken = access_token, refreshToken = refresh_token });
        }
        // 返回错误请求信息
        return BadRequest(new { code = 400, message = "登录失败,用户名或密码为空" });
    }
    //此方法用来刷新令牌,逻辑是验证refToken才能进入方法,进入后验证accessToken除了过期时间项的其他所有项,目的是防止用户修改权限等
    [HttpGet("refresh")]
    [Authorize(Roles = "refresh")]//验证权限
    public ActionResult Refresh(string accessToken)
    {
        var newAccessToken = _jwtHelper.RefreshToken(accessToken);
        //重新生成refeashToken
        var refresh_token = _jwtHelper.GenerateRefreshToken();
 
        // 返回成功信息,写出token
        return Ok(new
        {
            code = 200,
            message = "令牌刷新成功",
            refreshToken = refresh_token,
            accessToken = newAccessToken
        });
    }
}

 

5. 运行一下

 

 

 

获取到accessToken之后授权swagger (Bearer+" " + accessToken )

 

 

 

 

 

再请求一下刷新Token的接口

 

文章参考文档:

https://docs.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.jwt.jwtsecuritytokenhandler?view=azure-dotnet(官网)

https://www.cnblogs.com/zxy001126/p/15530864.html

https://www.cnblogs.com/hot-tofu-curd/p/15115844.html

posted @   Joni是只狗  阅读(1241)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示