ASP.NET Core 3.1使用JWT認證Token授權


0、引言
若不清楚什么是JWT的請先了解下什么是JWT。

1、關於Authentication與Authorization
我相信在aspnet core中剛接觸甚至用了段時間這兩個概念的時候都是一頭霧水的,傻傻分不清。
認證(Authentication)和授權(Authorization)在概念上比較的相似,且又有一定的聯系,因此很容易混淆。
認證(Authentication)是指驗證用戶身份的過程,即當用戶要訪問受保護的資源時,將其信息(如用戶名和密碼)發送給服務器並由服務器驗證的過程。
授權(Authorization)是驗證一個已通過身份認證的用戶是否有權限做某件事情的過程。
有過RBAC的開發經驗者來說這里可以這么通俗的來理解:認證是驗證一個用戶是否“合法”(一般就是檢查數據庫中是否有這么個用戶),授權是驗證這個用戶是否有做事情的權限(簡單理解成RBAC中的用戶權限)。

2、整個認證流程是怎樣的?

 

 從圖中可以看到整個認證、授權的流程,先進行身份驗證 ,驗證通過后將Token放回給客戶端,客戶端訪問資源的時候請求頭中添加Token信息,服務器進行驗證並於授權是否能夠訪問該資源。

3、開始JWT身份認證

3.1 引入nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

 3.2 在Startup.cs文件中進行配置

復制代碼
#region jwt驗證
            var jwtConfig = new JwtConfig();
            Configuration.Bind("JwtConfig", jwtConfig);
            services
                .AddAuthentication(option =>
                {
                    //認證middleware配置
                    option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        //Token頒發機構
                        ValidIssuer = jwtConfig.Issuer,
                        //頒發給誰
                        ValidAudience = jwtConfig.Audience,
                        //這里的key要進行加密
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecretKey)),
                        //是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比
                        ValidateLifetime = true,
                    };
                });
            #endregion
復制代碼

配置中間件

//啟用身份驗證功能。必須要在app.UseAuthorization();之前
            app.UseAuthentication();//鑒權,

JWTHelper

復制代碼
public class JWTHelper
    {
        public class JwtHelper
        {

            /// <summary>
            /// 頒發JWT字符串
            /// </summary>
            /// <param name="tokenModel"></param>
            /// <returns></returns>
            public static string IssueJwt(TokenModelJwt tokenModel)
            {
                // 自己封裝的 appsettign.json 操作類,看下文
                string iss = Appsettings.app(new string[] { "Audience", "Issuer" });
                string aud = Appsettings.app(new string[] { "Audience", "Audience" });
                string secret = Appsettings.app(new string[] { "Audience", "Secret" });

                var claims = new List<Claim>
              {
                 /*
                 * 特別重要:
                   1、這里將用戶的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方將這個 uid從 Token 中取出來,請看下邊的SerializeJwt() 方法,或者在整個解決方案,搜索這個方法,看哪里使用了!
                   2、你也可以研究下 HttpContext.User.Claims ,具體的你可以看看 Policys/PermissionHandler.cs 類中是如何使用的。
                 */                

                new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                //這個就是過期時間,目前是過期7200秒,可自定義,注意JWT有自己的緩沖過期時間
                new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(7200)).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Iss,iss),
                new Claim(JwtRegisteredClaimNames.Aud,aud),
                
                //new Claim(ClaimTypes.Role,tokenModel.Role),//為了解決一個用戶多個角色(比如:Admin,System),用下邊的方法
               };

                // 可以將一個用戶的多個角色全部賦予;
                // 作者:DX 提供技術支持;
                claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));


                //秘鑰 (SymmetricSecurityKey 對安全性的要求,密鑰的長度太短會報出異常)
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                
                var jwt = new JwtSecurityToken(
                    issuer: iss,
                    claims: claims,
                    signingCredentials: creds
                    //,expires:DateTime.Now.AddMinutes(1)
                    );

                var jwtHandler = new JwtSecurityTokenHandler();
                var encodedJwt = jwtHandler.WriteToken(jwt);

                return encodedJwt;
            }

            /// <summary>
            /// 解析
            /// </summary>
            /// <param name="jwtStr"></param>
            /// <returns></returns>
            public static TokenModelJwt SerializeJwt(string jwtStr)
            {
                var jwtHandler = new JwtSecurityTokenHandler();
                JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
                object role;
                try
                {
                    jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
                var tm = new TokenModelJwt
                {
                    Uid = (jwtToken.Id).ObjToInt(),
                    Role = role != null ? role.ObjToString() : "",
                };
                return tm;
            }
        }

        /// <summary>
        /// 令牌
        /// </summary>
        public class TokenModelJwt
        {
            /// <summary>
            /// Id
            /// </summary>
            public long Uid { get; set; }
            /// <summary>
            /// 角色
            /// </summary>
            public string Role { get; set; }
            /// <summary>
            /// 職能
            /// </summary>
            public string Work { get; set; }

        }
    }
復制代碼

 

然后呢 如果你使用的Swagger

加上自動在Header帶上Token

Bearer就是在ASP.NET Core 在 Microsoft.AspNetCore.Authentication 下實現了一系列認證, 包含 Cookie, JwtBearer, OAuth, OpenIdConnect 等
復制代碼
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Description = "JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間有一個空格)",
                    Name = "Authorization",//jwt默認的參數名稱,
                    In = ParameterLocation.Header,//jwt默認存放Autorization信息的位置(header中)
                    Type = SecuritySchemeType.ApiKey, //指定ApiKey
                    BearerFormat = "JWT",//標識承載令牌的格式 該信息主要是出於文檔目的
                    Scheme = "bearer"//授權中要使用的HTTP授權方案的名稱
                });
                //在Heder中添加Token 傳遞到后台
                options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme{
                            Reference = new OpenApiReference {
                                        Type = ReferenceType.SecurityScheme,
                                        Id = "Bearer"}
                        },new string[] { }
                    }
                });
復制代碼

 在對應Controller上或Action上加上Authorize標記

 

 

 驗證TOken

復制代碼
復制代碼
options.Events = new JwtBearerEvents
                {
                    //此處為權限驗證失敗后觸發的事件
                    OnChallenge = context =>
                    {
                        //此處代碼為終止.Net Core默認的返回類型和數據結果,這個很重要哦,必須
                        context.HandleResponse();
                        //自定義自己想要返回的數據結果,我這里要返回的是Json對象,通過引用Newtonsoft.Json庫進行轉換
                        var payload = JsonConvert.SerializeObject(new ApiResult<bool>("Token無效"));
                        //自定義返回的數據類型
                        context.Response.ContentType = "application/json";
                        //自定義返回狀態碼,默認為401 我這里改成 200
                        context.Response.StatusCode = StatusCodes.Status200OK;
                        //context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                        //輸出Json數據結果
                        context.Response.WriteAsync(payload);
                        return Task.FromResult(0);
                    }
                };
復制代碼

 

 /// <summary>
        /// 獲取Token
        /// </summary>
        /// <param name="req">請求流</param>
        /// <returns></returns>
        public static string GetToken(HttpRequest req)
        {
            string tokenHeader = req.Headers["Authorization"].ToString();
            if (string.IsNullOrEmpty(tokenHeader))
                throw new Exception("缺少token!");

            string pattern = "^Bearer (.*?)$";
            if (!Regex.IsMatch(tokenHeader, pattern))
                throw new Exception("token格式不對!格式為:Bearer {token}");

            string token = Regex.Match(tokenHeader, pattern).Groups[1]?.ToString();
            if (string.IsNullOrEmpty(token))
                throw new Exception("token不能為空!");

            return token;
        }
復制代碼

好了 基本沒問題 最后在如果請求401可以自定義

 

 

 

 

 

 整的代碼如下:

復制代碼
[AllowAnonymous]
        [HttpGet]
        [Route("api/auth")]
        public IActionResult Get(string userName, string pwd)
        {
            if (CheckAccount(userName, pwd, out string role))
            {
                //每次登陸動態刷新
                Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
                // push the user’s name into a claim, so we can identify the user later on.
                //這里可以隨意加入自定義的參數,key可以自己隨便起
                var claims = new[]
                {
                    new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                    new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
                    new Claim(ClaimTypes.NameIdentifier, userName),
                    new Claim("Role", role)
                };
                //sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                //.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
                var token = new JwtSecurityToken(
                    //頒發者
                    issuer: Const.Domain,
                    //接收者
                    audience: Const.ValidAudience,
                    //過期時間
                    expires: DateTime.Now.AddMinutes(30),
                    //簽名證書
                    signingCredentials: creds,
                    //自定義參數
                    claims: claims
                    );

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token)
                });
            }
            else
            {
                return BadRequest(new { message = "username or password is incorrect." });
            }
        }
復制代碼

3.3 然后改造一下StartUp.cs

  我們僅僅需要關心改動的地方,也就是AddJwtBearer這個驗證token的方法,我們不用原先的固定值的校驗方式,而提供一個代理方法進行運行時執行校驗

復制代碼
.AddJwtBearer(options =>

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateLifetime = true,//是否驗證失效時間
    ClockSkew = TimeSpan.FromSeconds(30),
    ValidateAudience = true,//是否驗證Audience
    //ValidAudience = Const.GetValidudience(),//Audience
    //這里采用動態驗證的方式,在重新登陸時,刷新token,舊token就強制失效了
    AudienceValidator = (m, n, z) =>
    {
        return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
    },
    ValidateIssuer = true,//是否驗證Issuer
    ValidIssuer = Const.Domain,//Issuer,這兩項和前面簽發jwt的設置一致
    ValidateIssuerSigningKey = true,//是否驗證SecurityKey
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
};
復制代碼

這里邏輯是這樣的:因為重新登陸將原來的變量更改了,所以這里校驗的時候也一並修改成了新的變量值,那么舊的token當然就不匹配了,也就是舊的token被強制失效了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 參考地址https://www.cnblogs.com/7tiny/p/11012035.html   https://www.cnblogs.com/cokeking/p/10969579.html


免責聲明!

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



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