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);
            }
        }

基本上就是这样了,有问题或者是有建议的欢迎留言评论或者私信我。

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