Asp.Net WebApi 使用JsonWebToken(JWT)身份驗證 ,大白話講解

JWT是什麼東西我就不說太多了,我就簡單介紹,要看官方一點的解釋可以百度,反正我是受不了看那個。

JWT現在比較流行的跨域認證解決方案,其最大的特點我認爲是:無需在服務端保存客戶端會話信息(session)

這個好理解,以往用戶login時,向服務端發起申請時都會收到來自服務端隨機生成的一個token信息,這個token信息會在服務端以及客戶端都保存下來,客戶端發起請求時帶上這個token,服務端驗證有效即可認爲是有效用戶操作行爲。這裏面的問題就是服務端的驗證,首先服務端必須在生成token時把它存儲到數據庫中,一般用redis(快)。服務端收到token,去數據庫取出key爲token值所對應的value信息,比如包含token的有效期啊,所對應的用戶ID啊,用戶名啊等等等等,這個時候服務端多了一個redis(數據庫)的需求,對不?

JWT很簡單,他也會生成一個token發送給客戶端,在生成token時你可以輸入有效期,用戶ID信息等(當然,你啥都不放也可以),但是是加密的,你可以理解爲他把你輸入的信息加密後生成了一個加密後的“字符串”,他把這個“字符串”發送給了客戶端使用。當服務端收到客戶端發來的“字符串”只需要對其解密,並解析出你輸入的信息,如果你生成token時輸入了有效期,那麼這個時候你解析出“有效期”跟當前時間做個比對不就知道有沒有過期了嗎?如果過期了,要求客戶端重新登錄,返回新的token即可。這個過程中服務端是完全不用去存儲token的。

有一個應用場景特別適合它,分佈式服務集羣,因爲無需存儲token到服務端,服務端不需要同步用戶登錄的token信息。並且服務器集羣在接收到同一用戶的token用相同的密鑰就能解析出一個有效且相同的用戶信息。

 

現在說一下C# (.Net) 的實現方式吧

我引用的庫

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

Startup.cs的配置,這裏我展示的只展示了需要添加的代碼

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            //JWT Authentication Configuration
            services.AddAuthentication(option =>
            {
                option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = true;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidAudience = Configuration["JsonWebToken:Audience"],//這裏讀取的是配置文件
                    ValidIssuer = Configuration["JsonWebToken:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JsonWebToken:SigningKey"]))//這是配置文件裏的密鑰
                };
            });
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            
            app.UseAuthentication();
            app.UseAuthorization();
        }

生成token

private async Task<LoginOM> IssueAccessToken(AccountInfo account)
        {
            try
            {
                var jsonWebToken = ConfigurationManager.GetJsonWebToken();
                var signinKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jsonWebToken.SigningKey));//密鑰
                int expiryInMinutes = Convert.ToInt32(jsonWebToken.ExpiryInMinutes);//有效期

                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new Claim[]
                    {
                        new Claim(JwtRegisteredClaimNames.Aud, jsonWebToken.Audience),
                        new Claim(JwtRegisteredClaimNames.Iss, jsonWebToken.Issuer),
                        new Claim(JwtRegisteredClaimNames.NameId, account.ID.ToString()),
                        new Claim(JwtRegisteredClaimNames.UniqueName, account.Guid),
                        new Claim(JwtRegisteredClaimNames.Exp, DateTime.UtcNow.AddMinutes(expiryInMinutes).ToString()),
                        new Claim(JwtRegisteredClaimNames.Email, account.Email)
                    }),
                    Expires = DateTime.UtcNow.AddMinutes(expiryInMinutes),
                    SigningCredentials = new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256Signature)//用密鑰簽名(加密)
                };

                var tokenHandler = new JwtSecurityTokenHandler();
                var createToken = tokenHandler.CreateToken(tokenDescriptor);
                var accessToken = tokenHandler.WriteToken(createToken);//生成的主角


                //Update Account
                account.LastLoginDate = DateTime.UtcNow;
                await UpdateAccount(account);//更新數據庫用戶登錄信息,比如最後一次登陸時間

                return new LoginOM//這個是我自己定義的輸出結構
                {
                    AccessToken = accessToken,//這是主角
                    ExpiresTime = tokenDescriptor.Expires.ToString()//返回給前端有效期
                };
            }
            catch (Exception ex)
            {
                Log.Error(ex, "AccountsComponent/IssueAccessToken Error");
                return null;
            }
        }

解析

        public static async Task<AccountInfo> GetUser(this ControllerBase controller)
        {
            var tokenString = GetTokenString(controller.Request.Headers["Authorization"].ToString());//從區塊頭獲取token信息

            JwtSecurityToken jst = new JwtSecurityTokenHandler().ReadJwtToken(tokenString);//解析

            var accountId = Convert.ToInt32(jst.Claims.Where(c => c.Type == JwtRegisteredClaimNames.NameId).FirstOrDefault().Value);//取用戶ID
            var expDate = TimeHelper.GetDateTime(jst.Claims.Where(c => c.Type == JwtRegisteredClaimNames.Exp).FirstOrDefault().Value);//取有效期

            if (expDate.CompareTo(DateTime.UtcNow) < 0)//有效期判斷
            {
                throw new BaseException(ErrorCode.ACCOUNT_LOGIN_TIMEOUT, "the login timeout");
            }

            var user = await new AccountsComponent().GetAccountById(accountId);//使用用戶ID查詢用戶信息
            if (!string.IsNullOrWhiteSpace(user.Guid) && Guid.Parse(user.Guid) != Guid.Empty)
            {
                return user;//沒有問題就返回
            }
            else
            {
                Log.Error(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name.ToString() + "(): " + accountId + ErrorMessage.Account_Not_Found.Message);
                throw new BaseException(ErrorMessage.Invalid_Credentials.Code, GeneralResources.InvalidCredentials);
            }
        }

基本上就是這樣了,有問題或者是有建議的歡迎留言評論或者私信我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章