.Net Core 基於JWT簽發Token


 

如果不了解JWT可以先了解這篇文章 。 這里主要是來記錄一下怎樣使用Jwt 自己來簽發和刷新Token,很多地方不符合實際使用,只是為了在這里測試達到效果,正式使用根據實際情況修改代碼

1. 添加Nuget引用

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

2. 添加簡單封裝的工具類

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 中參數配置)

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; 
     //配置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

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 去請求 

    [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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM