1. 程式人生 > 其它 >.NET 和 .NET Core 使用 JWT 授權驗證

.NET 和 .NET Core 使用 JWT 授權驗證

JWT介紹

參考文章 https://www.cnblogs.com/cjsblog/p/9277677.html

.NET 中使用

1. NuGet包

搜尋JWT,下載安裝(本人用的是8.2.3版本)

2. 自定義幫助類

2.1 新建 IHttpResponseResult interface介面

    public interface IHttpResponseResult
    {
    }

    /// <summary>
    /// 響應資料輸出泛型介面
    /// </summary>
    /// <typeparam name="T"></typeparam>
    // ReSharper disable once UnusedTypeParameter
    public interface IHttpResponseResult<T> : IHttpResponseResult
    {
    }

2.2 新建 HttpResponseResult 資料響應類

    public class HttpResponseResult<T> : IHttpResponseResult<T>
    {
        /// <summary>
        /// 狀態碼
        /// </summary>
        public int Code { get; set; }

        /// <summary>
        /// 訊息
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 資料
        /// </summary>
        public T Data { get; set; }

        /// <summary>
        /// 成功
        /// </summary>
        /// <param name="data">資料</param>
        /// <param name="msg">訊息</param>
        public HttpResponseResult<T> Success(T data = default, string msg = null)
        {
            Code = 0;
            Data = data;
            Message = msg;
            return this;
        }

        /// <summary>
        /// 失敗
        /// </summary>
        /// <param name="code">狀態碼</param>
        /// <param name="msg">訊息</param>
        /// <param name="data">資料</param>
        /// <returns></returns>
        public HttpResponseResult<T> Fail(T data = default, int code = -1, string msg = null)
        {
            Code = code;
            Message = msg;
            Data = data;
            return this;
        }
    }

    /// <summary>
    /// 響應資料靜態輸出
    /// </summary>
    public static class HttpResponseResult
    {
        /// <summary>
        /// 成功
        /// </summary>
        /// <param name="data">資料</param>
        /// <param name="msg">訊息</param>
        /// <returns></returns>
        public static IHttpResponseResult Success<T>(T data, string msg = "message")
        {
            return new HttpResponseResult<T>().Success(data, msg);
        }

        /// <summary>
        /// 失敗
        /// </summary>
        /// <param name="data">資料</param>
        /// <param name="msg">訊息</param>
        /// <param name="code">狀態碼</param>
        /// <returns></returns>
        public static IHttpResponseResult Fail<T>(T data, string msg = null, int code = -1)
        {
            return new HttpResponseResult<T>().Fail(data, code, msg);
        }
    }

3.生成JWT Token

3.1 新建Conteoller

新建 LoginController 並且新建一個生成 Token 的方法 Login

    /// <summary>
    /// JWT授權
    /// </summary>
    [RoutePrefix("api/login")]
    public class LoginController : ApiController
    {
        private readonly string secretKey = "JwtSecretKey";//Jwt金鑰,自定義
        private readonly int tokenExpire = 2;//Token過期時間

        /// <summary>
        /// 授權登入獲取Token
        /// </summary>
        /// <param name="loginModel"></param>
        /// <returns></returns>
        [HttpPost, Route("GetToken")]
        public IHttpResponseResult Login([FromBody] Login loginModel)
        {
            dynamic obj = new ExpandoObject();
            try
            {
                var userName = loginModel.UserName;
                var userPwd = loginModel.UserPwd;
                //var loginRes = ChatUserDao.GetJwtUser(userName, userPwd).IsNull(); //自己的資料庫驗證
                var loginRes = "Dennis".Equals(userName) && "123".Equals(userPwd);
                if (loginRes)
                {
                    return HttpResponseResult.Fail(obj, "The user is not found or password is error.");
                }

                var expireTime = DateTime.Now.AddHours(tokenExpire);
                //身份驗證資訊
                var jwtToken = new JwtToken { AuthUserName = userName, ExpireTime = expireTime };
                var key = Encoding.UTF8.GetBytes(secretKey);
                IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式
                IJsonSerializer serializer = new JsonNetSerializer(); //序列化Json
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); //base64加解密
                IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); //JWT編碼
                var token = encoder.Encode(jwtToken, key); //生成令牌

                obj.Token = token;
                obj.ExpireTime = expireTime;

                return HttpResponseResult.Success(obj, "Get Token Success.");
            }
            catch (Exception e)
            {
                LogFileHelper.WriteLog("GetToken", e.ToExceptionString());
                return HttpResponseResult.Fail(obj, e.ToExceptionString());
            }
        }
    }

3.2 新建JwtToken 模型類

    /// <summary>
    /// Jwt Token
    /// </summary>
    public class JwtToken
    {
        /// <summary>
        /// 授權者
        /// </summary>
        public string AuthUserName { get; set; }

        /// <summary>
        /// Token過期時間
        /// </summary>
        public DateTime ExpireTime { get; set; }
    }

4. 新建Attribute類驗證Token

App_Start專案檔案加中新建一個ApiAuthAttribute類,繼承自AuthorizeAttribute

    /// <summary>
    /// Jwt 授權驗證
    /// </summary>
    public class ApiAuthAttribute : AuthorizeAttribute
    {
        private readonly string secretKey = "JwtSecretKey";//加密祕鑰,與生成Token時的金鑰相同
        private const string authHeader = "Authorization";//請求頭Header中存放JwtToken的Key名稱

        /// <summary>
        /// 判斷授權
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        protected override bool IsAuthorized(HttpActionContext httpContext)
        {
            try
            {
                var httpHeader = httpContext.Request.Headers;
                var token = string.Empty;//獲取token

                foreach (var keyHeader in httpHeader)
                {
                    if (authHeader.Equals(keyHeader.Key))
                    {
                        token = keyHeader.Value.FirstOrDefault();
                    }
                }

                if (token.IsEmpty())
                {
                    return false;
                }

                var key = Encoding.UTF8.GetBytes(secretKey);
                IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式
                IJsonSerializer serializer = new JsonNetSerializer();//序列化Json
                IDateTimeProvider provider = new UtcDateTimeProvider();
                IJwtValidator validator = new JwtValidator(serializer, provider);
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密
                IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);

                //解密
                var json = decoder.DecodeToObject<JwtToken>(token, key, true);
                if (json.IsNull()) return false;
                return json.ExpireTime >= DateTime.Now;
            }
            catch (Exception e)
            {
                LogFileHelper.WriteLog(authHeader, e.ToExceptionString());
                return false;
            }
        }

        /// <summary>
        /// 授權失敗時呼叫
        /// </summary>
        /// <param name="httpContext"></param>
        protected override void HandleUnauthorizedRequest(HttpActionContext httpContext)
        {
            //Token過期時,響應頭新增過期標識
            httpContext.Response = httpContext.ControllerContext.Request.CreateResponse(
                HttpStatusCode.Unauthorized, HttpResponseResult.Fail(false, "Token is not found or expired, authorization failed."));
            httpContext.Response.Headers.Add("Token-Expired", "true");
        }
    }

5. 測試Token驗證

    /// <summary>
    /// 測試介面
    /// </summary>
    [RoutePrefix("api/test")]
    public class TestController : ApiController
    {
        /// <summary>
        /// Token認證測試
        /// </summary>
        /// <returns></returns>
        [HttpGet, Route("TokenAuth")]
        [ApiAuth]
        public IHttpResponseResult TokenAuth()
        {
            return HttpResponseResult.Success(true, "Auth Passed");
        }
    }

.NET Core 中使用

1. NuGet包

搜尋JwtBearer,下載安裝(本人安裝的是5.0.7)

2. 配置Startup

註冊JWT中介軟體,我是單獨寫了一個類然後引用,你也可以直接寫在Startup的ConfigureServices方法中

    /// <summary>
    /// JWT授權中介軟體
    /// </summary>
    public static class AuthorizationMiddleware
    {
        /// <summary>
        /// 註冊授權服務
        /// </summary>
        /// <param name="services"></param>
        public static void AddAuthorizationService(this IServiceCollection services)
        {
            // 開啟Bearer認證
            services.AddAuthentication(options =>
                {
                    // 設定預設使用jwt驗證方式
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                // 新增JwtBearer服務
                .AddJwtBearer(o =>
                {
                    // 令牌驗證引數
                    o.TokenValidationParameters = new TokenValidationParameters
                    {
                        // 設定生成token的祕鑰
                        //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(AppConfig.SecretKey)),
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfig.SecretKey)),
                        // 驗證祕鑰
                        ValidateIssuerSigningKey = true,
                        // 驗證釋出者
                        ValidateIssuer = true,
                        // 驗證Issure
                        ValidIssuer = AppConfig.Issuer,//發行人
                        // 驗證接收者
                        ValidateAudience = true,
                        // 讀配置Audience
                        ValidAudience = AppConfig.Audience,//訂閱人
                        // 驗證過期時間
                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.FromSeconds(30),
                        RequireExpirationTime = true
                    };
                    o.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            // 如果過期,則把<是否過期>新增到,返回頭資訊中
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }

                            return Task.CompletedTask;
                        }
                    };
                });

            //如果沒有角色控制到Action則註釋下列程式碼
            services.AddAuthorization(options =>
            {
                options.AddPolicy("User", policy => policy.RequireRole("User").Build());
                options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));

            });
        }
    }

然後在Startup的ConfigureServices方法中新增引用
services.AddAuthorizationService();

在Startup的Configure方法中使用授權
app.UseRouting()之後和app.UseEndpoints之前新增程式碼

app.UseAuthentication();//身份驗證
app.UseAuthorization();//身份授權

3. JWT自定義幫助類

3.1 JWTHelper

    /// <summary>
    /// JWT 幫助類
    /// </summary>
    public static class JWTHelper
    {
        /// <summary>
        /// 頒發JWT字串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJwt(UserModel tokenModel)
        {
            //獲取Appsetting配置
            var iss = AppConfig.Issuer;
            var aud = AppConfig.Audience;
            var secret = AppConfig.SecretKey;
            var expire = AppConfig.Expire;

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

                new Claim(JwtRegisteredClaimNames.Jti, tokenModel.UserId),
                new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                //這個就是過期時間,目前是過期1000秒,可自定義,注意JWT有自己的緩衝過期時間
                new Claim(JwtRegisteredClaimNames.Exp,
                    $"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
                new Claim(ClaimTypes.Expiration, DateTime.Now.AddMinutes(expire).ToFormatString()),
                new Claim(JwtRegisteredClaimNames.Iss, iss),
                new Claim(JwtRegisteredClaimNames.Aud, aud)
            };

            // 可以將一個使用者的多個角色全部賦予,比如引數System,Admin,那麼該token即擁有兩個角色;
            claims.AddRange(tokenModel.UserName.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

            //祕鑰 (SymmetricSecurityKey 對安全性的要求,金鑰的長度太短會報出異常)
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(iss, claims: claims, signingCredentials: credentials);

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

            return encodedJwt;
        }

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

金鑰等相關配置動態配置在appsettings.json檔案中

  "JwtSetting": {
    "Issuer": "DennisDong",
    "Audience": "https://www.dennisdong.top",
    "SecretKey": "D@1#n$n%i&s.D*0n!g",
    "Expire": "2"
  }

3.2 UserModel

    /// <summary>
    /// 使用者Model
    /// </summary>
    public class UserModel
    {
        /// <summary>
        /// UserId
        /// </summary>
        public string UserId { get; set; }

        /// <summary>
        /// 角色
        /// </summary>
        public string UserRole { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// 生日
        /// </summary>
        public DateTime UserBirthDay { get; set; }
    }

4. 生成和測試Token

    /// <summary>
    /// 登入
    /// </summary>
    [Route("api/login/[action]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        /// <summary>
        /// 登入獲取Token
        /// </summary>
        /// <param name="role"></param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult Login(string role)
        {
            string token;
            var result = false;
            if (role.NotNull())
            {
                //這裡只是測試,具體資料驗證根據自己需要來即可
                token = JWTHelper.IssueJwt(new UserModel
                {
                    UserId = Guid.NewGuid().ToString(), 
                    UserRole = role, 
                    UserName = role,
                    UserBirthDay = DateTime.Now
                });
                result = true;
            }
            else
            {
                token = " Login Fail.";
            }

            return Ok(new { Status = result, Token = token });
        }

        /// <summary>
        /// 解析Token
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        //該介面限制只有System 或 Admin 角色(中介軟體中AddAuthorization方法的配置決定)的Token可以訪問
        [Authorize("SystemOrAdmin")]
        //如果中介軟體中沒有配置AddPolicy,直接使用Authorize即可
        //[Authorize]
        public IActionResult ParseToken()
        {
            //需要擷取Bearer 
            var tokenHeader = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
            var user = JWTHelper.SerializeJwt(tokenHeader);
            return Ok(user);
        }
    }

Swagger配合JWT使用

請參考文章 https://www.cnblogs.com/dennisdong/p/15719616.html

WebAPI 跨域問題

請參考文章 https://www.cnblogs.com/dennisdong/p/15719873.html