SDK (JWT身份验证) 数据验证过滤器

 

参考资料:使用ActionFilterAttribute 记录 WebApi Action 请求和返回结果记录

客户端 (注意:SDK是客户端的东西,客户端调用SDK,通过SDK来请求我们的服务端)

什么是sdk,sdk其实就是做最傻瓜化的封装,别人能够很有好的调用你的方法

这里我们做一个客户端像服务端发起http请求的例子,在这个例子中我们封装一个.net的sdK

第一步:创建一个名字叫APISKD的类库

创建一个类:SDKResult.cs

namespace APISKD
{
    /// <summary>
    /// SDK返回类
    /// </summary>
    class SDKResult
    {
        /// <summary>
        /// 响应报文体(内容)
        /// </summary>
        public string Result { get; set; }
        /// <summary>
        /// 响应状态码
        /// </summary>
        public HttpStatusCode StatusCode { get; set; }
    }
}

创建一个类:SDKClient.cs

namespace APISKD
{
    /// <summary>
    /// 这个类主要供内部调用,所以是私有的
    /// </summary>
    class SDKClient
    {
        private string appKey;
        private string appSecret;
        private string serverRoot; //网站根目录 例如:http://127.0.0.1:80/api/v1

        public SDKClient(string appKey, string appSecret, string serverRoot)
        {
            this.appKey = appKey;
            this.appSecret = appSecret;
            this.serverRoot = serverRoot;
        }
        /// <summary>
        /// 异步Get请求
        /// </summary>
        /// <param name="url">要请求的地址:例如Home/Get</param>
        /// <param name="requestParamsDic">请求参数键值对</param>
        /// <returns></returns>
        public async Task<SDKResult> GetAsync(string url, IDictionary<string, object> requestParamsDic)
        {
            if (requestParamsDic == null)
            {
                throw new ArgumentNullException("querystring请求参数不能为null");
            }
            //对请求参数的key按照升序排序,然后再用等号将key和value连接起来:例如 name=lily
            var rpOrderItems = requestParamsDic.OrderBy(r => r.Key).Select(r => r.Key + "=" + r.Value);
            var requestParamsStr = string.Join("&", rpOrderItems); //用&号拼接请求参数;例如:age=26&name=lily

            //使用经过升序排序后的参数字符串+appSecret 对它进行MD5加密得到签名sign
            var sign = Md5Helper.CalcMD5(requestParamsStr + appSecret);

            using (HttpClient hc = new HttpClient())
            {
                hc.DefaultRequestHeaders.Add("AppKey", appKey);//将appKey添加到请求报文头中(做安全验证)
                hc.DefaultRequestHeaders.Add("Sign", sign);//将签名添加到请求报文头中(做安全验证)

                //请求全路径。例如:http://127.0.0.1:80/api/v1/Home/Get
                var requestUrl = Path.Combine(serverRoot, url);
                var resp = await hc.GetAsync(requestUrl + "?" + requestParamsStr);//发起异步请求

                SDKResult skdResult = new SDKResult();

                skdResult.Result = await resp.Content.ReadAsStringAsync(); //请求内容
                skdResult.StatusCode = resp.StatusCode; //请求状态码
                return skdResult;
            }
        }

        /// <summary>
        /// 异步Post请求
        /// </summary>
        /// <param name="url">要请求的地址:例如Home/Get</param>
        /// <param name="requestParamsDic">请求参数键值对</param>
        /// <returns></returns>
        public async Task<SDKResult> PostAsync(string url, Dictionary<string, string> requestParamsDic)
        {
            if (requestParamsDic == null)
            {
                throw new ArgumentNullException("querystring请求参数不能为null");
            }
            //对请求参数的key按照升序排序,然后再用等号将key和value连接起来:例如 name=lily
            var rpOrderItems = requestParamsDic.OrderBy(r => r.Key).Select(r => r.Key + "=" + r.Value);
            var requestParamsStr = string.Join("&", rpOrderItems); //用&号拼接请求参数;例如:age=26&name=lily

            //使用经过升序排序后的参数字符串+appSecret 对它进行MD5加密得到签名sign
            var sign = Md5Helper.CalcMD5(requestParamsStr + appSecret);


            using (HttpClient hc = new HttpClient())
            {
                FormUrlEncodedContent content = new FormUrlEncodedContent(requestParamsDic);

                //请求全路径。例如:http://127.0.0.1:80/api/v1/Home/Get
                var requestUrl = Path.Combine(serverRoot, url);
                var resp = await hc.PostAsync(requestUrl, content);

                SDKResult skdResult = new SDKResult();

                skdResult.Result = await resp.Content.ReadAsStringAsync();
                skdResult.StatusCode = resp.StatusCode; //请求状态码
                return skdResult;
            }
        }
    }
}

现在我们就添加一个暴露给外面调用的类,例如 用户操作类,我们暂且给它取名叫UserApi(只是命名中有个api,但是他不是一个api项目)

创建一个类:UserApi.cs (暴露在外,供客户端调用)

namespace APISKD
{
    /// <summary>
    /// 我们在客户端直接new这个类对象,传入参数,调用方法就可以了
    /// </summary>
    public class UserApi
    {
        private string appKey;
        private string appSecret;
        private string serverRoot;
        public UserApi(string appKey, string appSecret, string serverRoot)
        {
            this.appKey = appKey;
            this.appSecret = appSecret;
            this.serverRoot = serverRoot;
        }

        public async Task<long> AddAsync(string phoneNum, string nickName, string password)
        {
            SDKClient client = new SDKClient(appKey, appSecret, serverRoot);
            Dictionary<string, string> data = new Dictionary<string, string>();
            data["phoneNum"] = phoneNum;
            data["nickName"] = nickName;
            data["password"] = password;
            var result = await client.PostAsync("User/AddNew", data);
            if (result.StatusCode == System.Net.HttpStatusCode.OK)
            {
                //因为返回的报文体是新增id:{5}
                //使用newtonsoft把json格式反序列化为long
                long id = JsonConvert.DeserializeObject<long>(result.Result); //需要安装:Newtonsoft.Json
                return id;
            }
            else
            {
                throw new ApplicationException("新增失败,状态码" + result.StatusCode + ",响应报文" + result.Result);
            }
        }
    }
}

创建一个控制台应用程序:在里面调用我们封装的SDK类库的UserApi类,实现新增数据用户的目的

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            UserApi user = new UserApi("dfdfjpep","fdfjpejfefnmfdhfosh", "http://127.0.0.1:8888/api/v1/"); //初始化

            var result= user.AddAsync("18620998800", "tom", "123456").Result;//调用新增类(控制台Main方法不能用async所以这里用了Result)
        }
    }
}

服务端:WebApi

控制器

服务端中我们就简单的放了一个控制器方法

namespace WebApi.Controllers
{
    public class UserController : ApiController
    {
        public IUsersRepository users { get; set; }

        [HttpPost]
        public string GetData()
        {
            
            return "";
        }
    }
}

数据防篡改验证过滤器

并在webapi中我们还创建了一个身份验证的过滤器

namespace WebApi
{
    public class MyAuthenticationAttribute : IAuthorizationFilter//也可以直接继承AuthorizationFilterAttribute
    {
        public IAppInfosRepository app { get; set; }
        public bool AllowMultiple => true;

        public async Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            //获得报文头中的AppKey和Sign (我们与客户端约定,在向服务端发起请求的时候,将AppKey和Sign放到请求报文头中)
            IEnumerable<string> appKeys;
            if (!actionContext.Request.Headers.TryGetValues("AppKey", out appKeys)) //从请求报文头中获取AppKey
            {
                {
                    return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("报文头中的AppKey为空") };
                }
            }
            IEnumerable<string> signs;
            if (!actionContext.Request.Headers.TryGetValues("Sign", out signs)) //从请求报文头中获取Sign
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("报文头中的Sign为空") };
            }
            string appKey = appKeys.First();
            string sign = signs.First();
            var appInfo = await app.GetByAppKeyAsync(appKey);//从数据库获取appinfo这条数据(获取AppKey,AppSecret信息)
            if (appInfo == null)
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("不存在的AppKey") };
            }
            if (appInfo.IsEnable == "true")
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden) { Content = new StringContent("AppKey已经被封禁") };
            }

            string requestDataStr = ""; //请求参数字符串
            List<KeyValuePair<string, string>> requestDataList = new List<KeyValuePair<string, string>>();//请求参数键值对
            if (actionContext.Request.Method == HttpMethod.Post) //如果是Post请求
            {
                //获取Post请求数据  
                requestDataStr = GetRequestValues(actionContext);
                if (requestDataStr.Length > 0)
                {
                    string[] requestParamsKv = requestDataStr.Split('&');
                    foreach (var item in requestParamsKv)
                    {
                        string[] pkv = item.Split('=');
                        requestDataList.Add(new KeyValuePair<string, string>(pkv[0], pkv[1]));
                    }
                    //requestDataList就是按照key(参数的名字)进行排序的请求参数集合   
                    requestDataList = requestDataList.OrderBy(kv => kv.Key).ToList();
                    var segments = requestDataList.Select(kv => kv.Key + "=" + kv.Value);//拼接key=value的数组
                    requestDataStr = string.Join("&", segments);//用&符号拼接起来
                }

            }
            if (actionContext.Request.Method == HttpMethod.Get) //如果是Get请求
            {
                //requestDataList就是按照key(参数的名字)进行排序的请求参数集合          
                requestDataList = actionContext.Request.GetQueryNameValuePairs().OrderBy(kv => kv.Key).ToList();
                var segments = requestDataList.Select(kv => kv.Key + "=" + kv.Value);//拼接key=value的数组
                requestDataStr = string.Join("&", segments);//用&符号拼接起来
            }

            //计算Sign (即:计算requestDataStr+AppSecret的md5值)
            string computedSign = MD5Helper.ComputeMd5(requestDataStr + appInfo.AppSecret);

            //用户传进来md5值和计算出来的比对一下,就知道数据是否有被篡改过
            if (sign.Equals(computedSign, StringComparison.CurrentCultureIgnoreCase))
            {
                return await continuation();
            }
            else
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("sign验证失败") };
            }
        }


        /// <summary>
        /// 获取Post请求的请求参数内容
        /// 参考资料:https://www.cnblogs.com/hnsongbiao/p/7039666.html
        /// </summary>
        /// <param name="actionContext"></param>
        /// <returns></returns>
        public string GetRequestValues(HttpActionContext actionContext)
        {
            Stream stream = actionContext.Request.Content.ReadAsStreamAsync().Result;
            Encoding encoding = Encoding.UTF8;
            /*
                这个StreamReader不能关闭,也不能dispose, 关了就傻逼了
                因为你关掉后,后面的管道  或拦截器就没办法读取了
                所有这里不要用using
                using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.UTF8))
                {
                    result = reader.ReadToEnd().ToString();
                }
            */
            var reader = new StreamReader(stream, encoding);
            string result = reader.ReadToEnd();
            /*
            这里也要注意:   stream.Position = 0;
            当你读取完之后必须把stream的位置设为开始
            因为request和response读取完以后Position到最后一个位置,交给下一个方法处理的时候就会读不到内容了。
            */
            stream.Position = 0;
            return result;
        }
    }
}

这里提供一个JWT辅助类

using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using UserCenter.NETSDK;

namespace IM.Web
{
    public class JWTHelper
    {
        private static readonly string jwt_secret;

        static JWTHelper()
        {
            jwt_secret = ConfigurationManager.AppSettings["JWT_Secret"];
        }

        /// <summary>
        /// 把user加密放到JWT字符串中
        /// </summary>
        /// <param name="user"></param>
        /// <returns>JWT字符串</returns>
        public static string Encrypt(User user)
        {
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

            var token = encoder.Encode(user, jwt_secret);
            return token;
        }

        /// <summary>
        /// 从JWT token中解密出来User
        /// </summary>
        /// <param name="token"></param>
        /// <returns>如果token错误、被篡改或者过期,则返回null</returns>
        public static User Decrypt(string token)
        {
            try
            {
                IJsonSerializer serializer = new JsonNetSerializer();
                IDateTimeProvider provider = new UtcDateTimeProvider();
                IJwtValidator validator = new JwtValidator(serializer, provider);
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);

                var user = decoder.DecodeToObject<User>(token, jwt_secret, verify: true);
                return user;
            }
            catch (TokenExpiredException)
            {
                return null;
            }
            catch (SignatureVerificationException)
            {
                return null;
            }
        }

        /// <summary>
        /// 获得当前登录用户的User(供asp.net mvc用)
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        public static User GetUser(HttpContextBase httpContext)
        {
            var cookie = httpContext.Request.Cookies["JWTToken"];
            if(cookie==null)
            {
                return null;
            }
            string token = cookie.Value;
            return Decrypt(token);
        }

        /// <summary>
        /// 获得当前登录用户的User(供SignalR的Hub用)
        /// </summary>
        /// <param name="hubContext"></param>
        /// <returns></returns>
        public static User GetUser(HubCallerContext hubContext)
        {
            if(!hubContext.RequestCookies.ContainsKey("JWTToken"))
            {
                return null;
            }
            string token = hubContext.RequestCookies["JWTToken"].Value;
            return Decrypt(token);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Fanbin168/article/details/80775691