zoukankan      html  css  js  c++  java
  • WebApi 基于JWT实现Token签名认证

    开发提供数据的WebApi服务,最重要的是数据的安全性。那么对于我们来说,如何确保数据的安全是要思考的问题。

    在ASP.NET WebService服务中可以通过SoapHead验证机制来实现,那么在ASP.NET WebApi中我们应该如何保证我们的接口的安全呢?

    • 什么是JWT?

    JSON Web Token(JWT)是一个开放标准,它定义了一种紧凑的,自包含的方式,用于作为JSON对象在各方之间安全地传输信息,该信息可以被验证和信任,因为它是数字签名的
    简单理解就是一个字符串

    • JWT长什么样子?

    JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串

    • JWT构成

     1、第一部分头部header

    header典型的由两部分组成,token的类型("JWT")和算法名称(比如:HMAC HS256--哈希256)

    例如

    Base64({

        "alg":"HS256"

        "typ":"JWT"

    })

    然后用Base64对这个JSON编码就得到JWT的第一部分

    2、第二部分载荷Payload--载荷就是存放有效信息的地方
    Payload声明有3种类型
    a、register:预定义的

    b、public:公有的
    c、private:私有的
    2.1、标准中注册的声明 (建议但不强制使用) :
    iss: jwt签发者
    sub:jwt所面向的用户
    aud: 接收jwt的一方
    exp:jwt的过期时间,这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前,该jwt都是不可用的.
    iat:jwt的签发时间
    jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    2.2、公共的声明 :
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    2.3、私有的声明 :

     私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息

    例如:
    Base64({
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
    })

    对payload进行Base64编码就得到JWT的第二部分

    3、第三部分签名Signature

    为了得到签名部分,你必须有编码过的header,编码过的payload,一个秘钥,签名算法是header中指定的那个,然后对它们签名即可,例如:HMACSHA256(base64UrlEncode(header)+ "." + base64UrlEncode(payload),secret)。

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的签名算法配合secret进行签名,然后就构成了jwt的第三部分

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt

    签名是用于验证消息在传递过程中有没有更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方

    • 注意

    secret密钥是保存在服务器端的,jwt的签发生成是在服务器端做的,jwt的验签也是在服务器端做的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

    • 如何应用

    一般是在请求头里加入Authorization,并加上Bearer标注:

    • 基于JWT实现Token签名认证基本思路如下

    基本流程上是这样的:
    1、用户使用用户名和密码来请求服务器
    2、服务器进行验证用户的信息
    3、服务器通过验证,发送给用户一个token
    4、客户端存储token,并在每次请求时在request header附上这个token值,服务端并不存储token
    5、服务器验证token值,验证成功后,则返回数据

    • 实现原理图

    1、POST/user/login with username and password
    2、Create a JWT with a secret
    3、Returns the JWT to the Brower
    4、Sends the JWT on the Authorization Header
    5、Check JWT signature Get user info from the JWT
    6、Sends response to the client
    服务器端做两件事情,1--签发token值,2--验证token值
    JWT并不存储token

    • 代码具体实现   

    jwt官网:https://jwt.io/

    1、签发token

    1.1、ApiTokenService用于验证用户名和密码通过后签发token

    public class ApiTokenServiceController : ApiController
    {
        /// <summary>
        /// 获取访问口令令牌
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [HttpPost]
        public ResponseResult<LoginResponseResult> GetToken(string appId, string appSecret)
        {
            var result = ResponseResult<LoginResponseResult>.Default();
    
            if (string.IsNullOrEmpty(appId))
            {
                return ResponseResult<LoginResponseResult>.Faild("appid不能为空!");
            }
            if (string.IsNullOrEmpty(appSecret))
            {
                return ResponseResult<LoginResponseResult>.Faild("appsecret不能为空!");
            }
            
            try
            {
                //1、模拟从数据库中验证appId和appSecret
                if (appId.Equals("admin") &&
                    appSecret.Equals("123456"))
                {
                    //2、登录成功后将token以jwt字符串的形式返回给客户端。
                    var payload = new Dictionary<string, object>
                    {
                        { "iat", JwtHelper.ConvertDateTimeInt(DateTime.Now) },//token创建时间,unix时间戳。unix
                        { "exp", JwtHelper.ConvertDateTimeInt(DateTime.Now.AddMinutes(10)) },//expire 指定token的生命周期。unix
                        { "AppId",appId },
                        { "AppSecret", appSecret },
                    };
                    LoginResponseResult entity = new LoginResponseResult
                    {
                        AppId = appId,
                        AppSecret = appSecret,
                        Token = JwtHelper.GenerateJwtEncode(payload),
                    };
                    result = ResponseResult<LoginResponseResult>.Success(entity, "获取token成功!");
                }
                else
                {
                    result = ResponseResult<LoginResponseResult>.Faild("获取token失败!");
                }
            }
            catch (System.Exception ex)
            {
                result = ResponseResult<LoginResponseResult>.Exception(ex.Message);
            }
            return result;
        }
    
        /// <summary>
        /// 刷新新的口令令牌
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [HttpPost]
        public ResponseResult<LoginResponseResult> RefreshToken()
        {
            string tokenParam = WebHelper.GetHeadersParamValue("token");
    
            var result = ResponseResult<LoginResponseResult>.Default();
    
            if (string.IsNullOrEmpty(tokenParam))
            {
                return ResponseResult<LoginResponseResult>.Faild("token不能为空!");
            }
    
            var tokenVaule = JwtHelper.GetJwtDecode(tokenParam);
            if (tokenVaule == null)
            {
                var resp = Request.CreateResponse<ResponseResult>(HttpStatusCode.OK, ResponseResult.NotAuthorization("token过期或无效!"), "application/json");
                throw new HttpResponseException(resp);
            }
            try
            {
    
                string appId = tokenVaule["AppId"].ToString();
                string appSecret = tokenVaule["AppSecret"].ToString();
    
                //2、登录成功后将token以jwt字符串的形式返回给客户端。
                //对象初始化器
                var payload = new Dictionary<string, object>
                    {
                        { "iat", JwtHelper.ConvertDateTimeInt(DateTime.Now) },//token创建时间,unix时间戳。unix
                        { "exp", JwtHelper.ConvertDateTimeInt(DateTime.Now.AddMinutes(10)) },//expire 指定token的生命周期。unix
                        { "AppId",appId },
                        { "AppSecret",appSecret  },
                    };
                LoginResponseResult entity = new LoginResponseResult
                {
                    AppId = appId,
                    AppSecret = appSecret,
                    Token = JwtHelp.GenerateJwtEncode(payload),
                };
                result = ResponseResult<LoginResponseResult>.Success(entity, "口令刷新成功!");
            }
            catch (System.Exception ex)
            {
                result = ResponseResult<LoginResponseResult>.Exception(ex.Message);
            }
            return result;
        }
    }

    1.2、响应结果实体ResponseResult

    public class ResponseResult
    {
        public ResponseResult()
        {
        }
    
        /// <summary>
        /// 状态
        /// </summary>
        public int RequestStatus
        {
            get;
            set;
        }
    
        /// <summary>
        /// 消息内容
        /// </summary>
        public string Msg
        {
            get;
            set;
        }
    
        public static ResponseResult Default()
        {
            var result = new ResponseResult();
            result.RequestStatus = (int)ResponseResultStatus.Default;
            result.Msg = "";
            return result;
        }
    
        public static ResponseResult Success(string message = "")
        {
            var result = new ResponseResult();
            result.RequestStatus = (int)ResponseResultStatus.Succeed;
            result.Msg = message;
            return result;
        }
    
        public static ResponseResult Exception(string message)
        {
            var result = new ResponseResult();
            result.RequestStatus = (int)ResponseResultStatus.Exception;
            result.Msg = message;
            return result;
        }
    
        public static ResponseResult Faild(string message)
        {
            var result = new ResponseResult();
            result.RequestStatus = (int)ResponseResultStatus.Faild;
            result.Msg = message;
            return result;
        }
    
        public static ResponseResult NotAuthorization(string message)
        {
            var result = new ResponseResult();
            result.RequestStatus = (int)ResponseResultStatus.NotAuthorization;
            result.Msg = message;
            return result;
        }
    }
    
    public class ResponseResult<T> : ResponseResult
        where T : class, new()
    {
        public ResponseResult()
        {
            this.Data = new T();
        }
    
        public T Data
        {
            get;
            set;
        }
    
        public static ResponseResult<T> Default()
        {
            var result = new ResponseResult<T>();
            result.Data = default(T);
            result.RequestStatus = (int)ResponseResultStatus.Default;
            result.Msg = "";
            return result;
        }
    
        public static ResponseResult<T> Success(T t, string message = "")
        {
            var result = new ResponseResult<T>();
            result.Data = t;
            result.RequestStatus = (int)ResponseResultStatus.Succeed;
            result.Msg = message;
            return result;
        }
    
        public static ResponseResult<T> Exception(string message)
        {
            var result = new ResponseResult<T>();
            result.Data = default(T);
            result.RequestStatus = (int)ResponseResultStatus.Exception;
            result.Msg = message;
            return result;
        }
    
        public static ResponseResult<T> Faild(string message)
        {
            var result = new ResponseResult<T>();
            result.Data = default(T);
            result.RequestStatus = (int)ResponseResultStatus.Faild;
            result.Msg = message;
            return result;
        }
    
        public static ResponseResult<T> NotAuthorization(string message)
        {
            var result = new ResponseResult<T>();
            result.Data = default(T);
            result.RequestStatus = (int)ResponseResultStatus.NotAuthorization;
            result.Msg = message;
            return result;
        }
    }  
    
    public enum ResponseResultStatus
    {
        Default = 0,
        Succeed = 100,
        Faild = 101,
        Exception = 102,
        NotAuthorization = 403
    }

    1.3、LoginResponseResult

    public class LoginResponseResult 
    {
        public string AppId { get; set; }
        public string AppSecret { get; set; }
        public string Token { get; set; }
    }

    2、JwtHelper

    通过nuget引用jwt组件,掌握JWT组件的使用,学会使用JWT组件生成token值,以及验证token值

    public class JwtHelper
    {
        /// <summary>
        /// jwt的secret千万不能泄密了,只能让服务端自己知道。jwt的secret用户服务端token的签发和验证。
        /// </summary>
        private static string secret = "davidmengdavidmeng";
    
        /// <summary>
        /// 生成JwtToken
        /// </summary>
        /// <param name="payload"></param>
        /// <returns></returns>
        public static string GenerateJwtEncode(Dictionary<string, object> payload)
        {
            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);
            return token;
        }
    
        /// <summary>
        /// 根据jwtToken获取payload
        /// </summary>
        /// <param name="token">jwtToken</param>
        /// <returns></returns>
        public static IDictionary<string, object> GetJwtDecode(string token)
        {
            return GetJwtDecode<Dictionary<string, object>>(token);
        }
    
        public static T GetJwtDecode<T>(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);
                return decoder.DecodeToObject<T>(token, secret, verify: true);
            }
            catch (TokenExpiredException)
            {
                Console.WriteLine("Token has expired");
            }
            catch (SignatureVerificationException)
            {
                Console.WriteLine("Token has invalid signature");
            }
            return default(T);
        }
    
        /// <summary>  
        /// Unix时间戳转为C#格式时间  
        /// </summary>  
        /// <param name="timeStamp">Unix时间戳格式,例如1482115779</param>  
        /// <returns>C#格式时间</returns>  
        public static DateTime GetTime(string timeStamp)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long lTime = long.Parse(timeStamp + "0000000");
            TimeSpan toNow = new TimeSpan(lTime);
            return dtStart.Add(toNow);
        }
    
        /// <summary>  
        /// DateTime时间格式转换为Unix时间戳格式  
        /// </summary>  
        /// <param name="time"> DateTime时间格式</param>  
        /// <returns>Unix时间戳格式</returns>  
        public static int ConvertDateTimeInt(System.DateTime time)
        {
            System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
            return (int)(time - startTime).TotalSeconds;
        }
    }

    3、BaseApiTokenController

     让业务接口继承自BaseApiTokenController,而BaseApiTokenController继承自ApiController,实现重写Initialize方法,在里面实现用JWT组件验证用户的JWT Token的有效性(用户token口令无效或者是否已经过期),这样每一个继承自BaseApiTokenController的业务接口都将验证token

    public class BaseApiTokenController : ApiController
    {
        protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
    
            string tokenParam = WebHelper.GetHeadersParamValue("token");
    
            #region 1、验证token、参数合法性
            if (string.IsNullOrEmpty(tokenParam))
            {
                var resp = Request.CreateResponse<ResponseResult>(HttpStatusCode.OK, ResponseResult.NotAuthorization("token参数不能为空!"), "application /json");
                throw new HttpResponseException(resp);
            }
            #endregion
    
            #region 2、验证用户的JWT Token的有效性(用户token口令无效或者是否已经过期)
            var tokenVaule = JwtHelper.GetJwtDecode(tokenParam);
            if (tokenVaule == null)
            {
                var resp = Request.CreateResponse<ResponseResult>(HttpStatusCode.OK, ResponseResult.NotAuthorization("token过期或无效!"), "application/json");
                throw new HttpResponseException(resp);
            }
            #endregion
        }
    }

    订单服务OrderServiceController

    public class OrderServiceController : BaseApiTokenController
    {
        [HttpGet]
        [HttpPost]
        public IEnumerable<string> GetOrder()
        {
            return new string[] { "ASP.NET WebApi 基于JWT实现Token签名认证" };
        }
    }

    4、界面
    4.1、登录输入正确的用户名和密码

     4.2、数据库验证用户名和密码通过,签发token

    4.3、获取到token后,跳转到另外一个界面,单击获取订单按钮访问订单服务
    请求时把jwt token,加入到header中

    4.4、订单服务通过token验证,进入到订单服务

  • 相关阅读:
    韩信的糊涂
    用友U8两个怪问题
    写给对前途迷茫的朋友:五句话定会改变你的人生
    换博客了
    各种杀毒工具的优缺点
    又是一年中秋到 别有一般更思乡
    谁说黑夜是孤单的
    李想:创业不一定是创办企业
    SQ小组KTV点歌系统简介
    注意!在subList生成子列表之后,一定不要随便更改原列表
  • 原文地址:https://www.cnblogs.com/menglin2010/p/12502279.html
Copyright © 2011-2022 走看看