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驗證,進入到訂單服務


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM