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验证,进入到订单服务

posted @ 2020-03-22 19:45  David.Meng  阅读(3566)  评论(1编辑  收藏  举报