WebApi JWT 身份验证

525 阅读4分钟

.Net 使用JWT

JWT

  • 什么是JSON Web Token?

SON Web Token(以下简称 JWT)是一套开放的标准(RFC 7519),它定义了一套简洁(compact)且 URL 安全(URL-safe)的方案,以安全地在客户端和服务器之间传输 JSON 格式的信息。

  • 它有什么优点?
  • 体积小(一串字符串)。因而传输速度快
  • 传输方式多样。可以通过 HTTP 头部(推荐)/URL/POST 参数等方式传输
  • 严谨的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持应用定制
  • 支持跨域验证,多应用于单点登录
  • 为什么使用JWT?

充分依赖无状态 API ,契合 RESTful 设计原则(无状态的 HTTP)

JWT的Token组成部分

  • header:对token的类型以及加密方法进行base64加密得到;
  • payload:对有效信息进行base64加密得到;
  • signature:对base64加密后的header和base64加密后的payload使用'.'组合为字符串,再通过header中指定的加密方式加secret组合加密;

他是怎么鉴别客户端传来的tojen是否被篡改的?

在我们收到客户端的token后,将token的第一和第二部分,再次进行组合加密第三部分的过程,得到一个signature。如果这个singature和token里的singnature对比不同,则token被篡改。 因此我们服务器端必须保留secret且不能泄露,否则客户端可以自行签发。

如何使用JWT

  • 加密
    • 引入JWT程序包
  • 生成token
        private static string Secret = "serect";
        static void Main(string[] args)
        {
        	//如果设定过期时间,一定的是秒数
            var payload = new Dictionary<string, object>() {
                {"name","huangwei" },
            };
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlencoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlencoder);
            var token = encoder.Encode(payload, Secret);
            Console.WriteLine(token);
            Console.ReadKey();
        }
    
        private static string Secret = "serect";
        static void Main(string[] args)
        {
            IDateTimeProvider provider = new UtcDateTimeProvider();
            var timeSpan=provider.GetNow().AddHours(1).ToUnixTimeSeconds();
            var payload = new Dictionary<string, object>() {
                {"name","huangwei" },
                {"exp",timeSpan},
            };
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlencoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlencoder);
            var token = encoder.Encode(payload, Secret);
            Console.WriteLine(token);
            Console.ReadKey();
        }
    }
    

WebApi中使用JWT进行身份验证

  • 发起登录请求,并传递参数
  • 接收参数,验证登录逻辑,登陆成功则返回token
  • 客户端接受,保存token,请求权限api,并将token附加在header头
  • 服务器验证身份,如果token不存在或token被篡改,验证失败,没有权限获得数据
  • 未使用过滤器的身份验证,略显代码冗余
    public class JwtTools
    {
        //密钥,
        public static string key = "love this world and love you too";
        public static string Encode(Dictionary<string,object> payload,long expTime,string key)
        {
            key = key == null ? key : JwtTools.key;
            payload.Add("exp", expTime);
            IJsonSerializer serializer = new JsonNetSerializer();
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, base64UrlEncoder);
            //通过header.payload 用 key加密 生成第三段 signature 并返回三段完整token
            var token = encoder.Encode(payload, key);
            return token;
        }
        private static string DeEncode(string token,string key)
        {
            try
            {
                key = key == null ? key : JwtTools.key;
                IJsonSerializer jsonSerializer = new JsonNetSerializer();
                var provider = new UtcDateTimeProvider();
                IJwtValidator jwtValidator = new JwtValidator(jsonSerializer, provider);
                IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
                IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
                IJwtDecoder decoder = new JwtDecoder(jsonSerializer,jwtValidator,base64UrlEncoder,algorithm);
                var json = decoder.Decode(token, key,verify:true);
                return json;
            }
            catch (TokenExpiredException)
            {
                throw;
            }
            catch (SignatureVerificationException)
            {
                throw;
            }
        }
        public static string ValidateLogin(HttpRequestHeaders headers, string key)
        {
            if (headers.GetValues("token") == null || !headers.GetValues("token").Any())
            {
                throw new Exception("请登录");
            }
            return DeEncode(headers.GetValues("token").First(), key);
        }
    }
        [HttpPost]
        [Route("Login")]
        public string Login(dynamic model)
        {
            if(model.userName.ToString().Length>2 && model.userPwd.ToString() == "123456")
            {
                return JwtTools.Encode(new Dictionary<string, object>(){
                    {"name",model.userName.ToString() }
                },DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds(),JwtTools.key);
            }
            throw new Exception("登录失败");
        }
        [HttpGet]
        [Route("getMs")]
        public string GetAccoutMS()
        {
            var json = JwtTools.ValidateLogin(Request.Headers, JwtTools.key);
            //首先我们要知道webApi中 http无状态
            return json;
        }
    }

使用这种方法可以实现身份验证,但是如果每个Action都需要通过身份验证,我们岂不是需要在每一个Action中都写一次调用。显然很麻烦。

因此我们有第二种方法,过滤器,当每一次请求到达时,都将先执行过滤器的方法,如果通过则将执行Action。符合asp.net管道事件。

  • 过滤器
    • 1.使用过滤器对JWT进行验证
    • 2.将验证过后得到的结果赋给User.Identity......
    • 3.因为User和Identity是接口类型,因此我们可以实现这些接口,并在过滤器中赋值给User和Identity

    过滤器

    public class MyAuth : Attribute, IAuthorizationFilter
    {
        public bool AllowMultiple { get; }
    
        public async Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            IEnumerable<string> headers;
            if(actionContext.Request.Headers.GetValues("token")==null|| actionContext.Request.Headers.TryGetValues("token",out headers) == false)
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
            var logName = JwtTools.DeEncode<Dictionary<string,object>>(headers.First())["user"];
            var userID = JwtTools.DeEncode<Dictionary<string,object>>(headers.First())["userID"];
            //过期时间
            var expTime = (long)JwtTools.DeEncode<Dictionary<string, object>>(headers.First())["exp"];
            if(DateTimeOffset.UtcNow.ToUnixTimeSeconds() > expTime)
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
            (actionContext.ControllerContext.Controller as ApiController).User = new AppUser(logName.ToString(), int.Parse(userID.ToString()));
           return await continuation();
        }
    }
    

    实现User和Identity

    public class AppUser : IPrincipal
    {
        public AppUser(string name,int id)
        {
            Identity = new AppIdentity(name, id);
        }
        public IIdentity Identity { get; }
    
        public bool IsInRole(string role)
        {
            throw new NotImplementedException();
        }
    }
    public class AppIdentity : IIdentity
    {
        public AppIdentity(string name,int id)
        {
            Name= name;
            Id = id;
        }
        public string Name { get; }
        public int Id { get;}
    
        public string AuthenticationType { get; }
    
        public bool IsAuthenticated { get; }
    }
    

    JWTTools

    public class JwtTools
    {
        public static string key = "huangwei@";
        public static string Encode(Dictionary<string,object> payload,string key=null)
        {
            key = string.IsNullOrEmpty(key) ? JwtTools.key : key;
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, base64UrlEncoder);
            payload.Add("exp",DateTimeOffset.UtcNow.AddHours(2).ToUnixTimeSeconds());
            var json=encoder.Encode(payload, key);
            return json;
        }
        public static T DeEncode<T>(string token, string key = null)
        {
            key = string.IsNullOrEmpty(key) ? JwtTools.key : key;
            try
            {
                var provider = new UtcDateTimeProvider();
                IJsonSerializer jsonSerializer = new JsonNetSerializer();
                IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
                IJwtValidator validator = new JwtValidator(jsonSerializer, provider);
                IJwtAlgorithm jwtAlgorithm = new HMACSHA256Algorithm();
                IJwtDecoder decoder = new JwtDecoder(jsonSerializer, validator, base64UrlEncoder, jwtAlgorithm);
                var json = decoder.Decode(token, key, verify: true);
                return JsonConvert.DeserializeObject<T>(json);
            }
            catch (TokenExpiredException)
            {
                throw;
            }
            catch (SignatureVerificationException)
            {
                throw;
            }
        }
    }
    
  • WebApi支持数据注解