1. 程式人生 > >asp.net web api 接口安全與角色控制

asp.net web api 接口安全與角色控制

unix時間戳 客戶 unix時間 space nal gui 接口安全 不同的 ict

1 API接口驗證與授權

JWT

JWT定義,它包含三部分:headerpayloadsignature;每一部分都是使用Base64編碼的JSON字符串。之間以句號分隔。signature”header.payload”經加密後的字符串。

采用JWT實現驗證與授權檢驗機制,JWT格式為:

header

{

"typ": "JWT",

"alg": "HS256"

}

payloadappid為GUID,timestampunix時間戳

{

"appid": GUID,

"timestamp": Unix time

}

Signature使用HS256

HMAC SHA-256,SHA Secure Hash Algorithm,安全散列算法headerpayload‘.’連接的字符串進行簽名。

JWT加密:采用RSA加密算法對其進行加密。

密鑰發放

發放給客戶端的參數:appIdappSecretpublicKeyprivateKeyId。其中publicKeyRSA公鑰,privateKeyId為服務端私鑰Id。服務端或根據privateKeyId在緩存(本地或Redis等)中查找RSA私鑰。

合成accessToken:header、payload與上述相同,簽名密鑰為appSecret。合成以後,使用publicKey對其進行加密。

合成headerJson:accessToken和privateKeyId構成的Json字符串,然後將字符串用Base64編碼方式編碼。

驗證流程

客戶端將上述headerB64放入請求頭,向服務端發起請求,服務端從請求頭中拿到headerJson並解碼headerJson,進而從中得到accessTokenprivateKeyId,服務端根據privateKeyId找到privateKey,使用privateKeyaccessToken解密,根據payload中的timestamp驗證過期,若未過期,那麽進行簽名校驗,驗證通過授權用戶端。

示例代碼(關鍵性代碼)

public abstract
class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter { public async Task AuthenticateAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken) { await Task.Factory.StartNew(()=> { //解析頭信息,獲得appid和timestamp var header = ... //如果未獲得上述信息 if (header == null) { context.ErrorResult = new AuthenticationFailureResult(requestHeaderAnalysis.ExecStatus, context.Request); return; } //從緩存中獲得RSA私鑰 string privateKey= ... if (String.IsNullOrWhiteSpace(privateKey)) { context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B07", "a001"), context.Request); return; } //使用RSA私鑰對AccessToken解密 string accessToken = Decrypt(requestHeaderInfo.AccessToken, privateKey); if (String.IsNullOrWhiteSpace(accessToken)) {//驗證憑據是空,設置錯誤信息 context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B05", "a001"), context.Request); return; } //從AccessToken的payload中獲得appKey和timestamp(時間戳) var payloadDict = JsonWebToken.DecodeToObject(accessToken); string appKey = Convert.ToString(payloadDict["appKey"]); string timestamp = Convert.ToString(payloadDict["timestamp"]); //在服務端數據庫中,根據appKey查找appSecret ApiAccount apiAccount = GetApiAccount(appKey); if (apiAccount==null||string.IsNullOrWhiteSpace(apiAccount.AppSecret)) { context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B05", "a001"), context.Request); return; } //驗證是否超時,簽名是否被篡改 try { //允許的時間段(小時轉化為秒) JsonWebToken.Validate(accessToken, apiAccount.AppSecret, (int)AppSettings.TokenTimeout.TotalSeconds); } catch (TokenExpiredException ex) { context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B03", "a001"), context.Request); return; } catch (SignatureVerificationException ex) { context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B02", "a001"), context.Request); return; } }); //其他驗證邏輯 await AuthenticateHockAsync(context, cancellationToken); } //// <summary> /// 子類中重寫 /// 實現他驗證邏輯 /// </summary> protected abstract Task AuthenticateHockAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken); /// <summary> /// 設置principal /// </summary> public Task ChallengeAsync(HttpAuthenticationChallengeContext context, System.Threading.CancellationToken cancellationToken) { return Task.FromResult(0); } public bool AllowMultiple { get { return true; } } }

2 用戶授權

某些數據只有用戶登陸了才能夠獲得,並且不同的用戶對數據的訪問級別也不一樣,為實現登陸驗證與角色控制,采用以下方式。

在上述實現API接入權限驗證的基礎上,為headerJson增加一個字段:loginToken;和accessToken相似,loginToken也是JWT標準字符串,不同的是loginToken的payload部分,loginToken的payload結構為:

{

"identifyingCode": GUID,

"account":userAccount

"timestamp": Unix time

}

其中:identifyingCode值為GUID,account為用戶賬號,timestampUNIX時間戳。

客戶端不生成loginToken,在客戶端合成accessToken後,調用服務端的登陸方法,成功登陸後獲得loginToken。

服務端驗證流程

客戶端調用登陸方法的同時,如果登陸成功,服務端會將登陸信息存儲到緩存中,主要的就是loginToken,根據業務需要可以增加其他信息。每一個loginToken對應了一個鍵值,這裏使用useAccount,即用戶賬號作為鍵值。服務端獲得loginToken後,根據privateKeyId(headerJson字段之一)獲得privateKey對loginToken解密,根據payload中的timestamp驗證是否過期,然後驗證簽名是否正確,接著根據account找到上次登陸時服務端緩存中存儲的loginToken,比較本次loginToken中的identifyingCode是否與上次一樣,不一樣表明,其在另一臺設備登陸過。

單設備登陸:

某些情形下,不允許多設備同時使用同一賬號登陸或多人同時使用同一賬號,上述方法采用loginToken中添加identifyingCode字段來控制多設備同時使用同一賬號的情形。

示例代碼(關鍵性代碼)

public class LoginAuthenticationAttribute : BasicAuthenticationAttribute
    {
        protected override async Task AuthenticateHockAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken)
        {
            await Task.Factory.StartNew(() => 
            {
                //解析頭信息,獲得appid和timestamp
                var header = ...
                //如果未獲得上述信息
                if (header == null)
                {
                    context.ErrorResult = new AuthenticationFailureResult(...);
                    return;
                }


                //獲得LoginToken
                if (String.IsNullOrWhiteSpace(requestHeaderInfo.LoginToken))
                { //驗證憑據是空,設置錯誤信息
                    context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B01", "a002"), context.Request);
                    return;
                }

                //從loginToken的payload中獲得account,timestamp(時間戳)
                var payloadDict = JsonWebToken.DecodeToObject(requestHeaderInfo.LoginToken);
                string identifyingCode = Convert.ToString(payloadDict["identifyingCode"]);
                string account = Convert.ToString(payloadDict["account"]);
                string timestamp = Convert.ToString(payloadDict["timestamp"]);
                //從緩存中獲得LoginToken
                LoginInfoDAL loginInfoDAL = new LoginInfoDAL(AppSettings.TokenTimeout);
                LoginCacheModel loginInfo = loginInfoDAL.GetLoginInfo(account);
                if (loginInfo == null)
                {
                    context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("C13", "a002"), context.Request);
                    return;
                }

                //比較客戶端傳入LoginToken和緩存中的LoginToken的userId
                var payloadDictCache = JsonWebToken.DecodeToObject(loginInfo.LoginToken);
                string identifyingCodeCache = Convert.ToString(payloadDictCache["identifyingCode"]);

                if (identifyingCodeCache != identifyingCode)
                {//不相等,提示在另一臺設備登陸
                    context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("C08", "a002"), context.Request);
                    return;
                }

                //得到密鑰
                TokenKeyDAL tokenKeyDAL = new TokenKeyDAL(AppSettings.TokenTimeout);
                string loginTokenKey = tokenKeyDAL.GetTokenKey(account);
                if (string.IsNullOrWhiteSpace(loginTokenKey))
                {
                    context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B04","a002"), context.Request);
                    return;
                }

                //驗證是否超時,LoginToken是否被篡改
                try
                {
                    //允許的時間段(小時轉化為秒)
                    int allowSpan = (int)AppSettings.TokenTimeout.TotalSeconds;
                    JsonWebToken.Validate(requestHeaderInfo.LoginToken, loginTokenKey, allowSpan);
                }
                catch (TokenExpiredException ex)
                {
                    context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B03", "a002"), context.Request);
                }
                catch (SignatureVerificationException ex)
                {
                    context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B02", "a002"), context.Request);
                }
            });
        }
    }

asp.net web api 接口安全與角色控制