WebApi安全性 使用TOKEN+簽名驗證

首先問大家一個問題,你在寫開放的API接口時是如何保證數據的安全性的?先來看看有哪些安全性問題在開放的api接口中,我們通過http Post或者Get方式請求服務器的時候,會面臨著許多的安全性問題,例如:

請求來源(身份)是否合法?

請求參數被篡改?

請求的唯一性(不可復制),防止請求被惡意攻擊

為了保證數據在通信時的安全性,我們可以采用TOKEN+參數簽名的方式來進行相關驗證。



比如說我們客戶端需要查詢產品信息這個操作來進行分析,客戶端點擊查詢按鈕==》調用服務器端api進行查詢==》服務器端返回查詢結果

一、不進行驗證的方式

api查詢接口:

客戶端調用:http://api.XXX.com/getproduct?id=value1

如上,這種方式簡單粗暴,在瀏覽器直接輸入"http://api.XXX.com/getproduct?id=value1",即可獲取產品列表信息了,但是這樣的方式會存在很嚴重的安全性問題,沒有進行任何的驗證,大家都可以通過這個方法獲取到產品列表,導致產品信息泄露。

那么,如何驗證調用者身份呢?如何防止參數被篡改呢?如何保證請求的唯一性? 如何保證請求的唯一性,防止請求被惡意攻擊呢?


二、使用TOKEN+簽名認證 保證請求安全性

token+簽名認證的主要原理是:1.做一個認證服務,提供一個認證的webapi,用戶先訪問它獲取對應的token

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2.用戶拿著相應的token以及請求的參數和服務器端提供的簽名算法計算出簽名后再去訪問指定的api

               ?3.服務器端每次接收到請求就獲取對應用戶的token和請求參數,服務器端再次計算簽名和客戶端簽名做對比,如果驗證通過則正常訪問相應的api,驗證失敗則返回具體的失敗信息


具體代碼如下 :

1.用戶請求認證服務GetToken,將TOKEN保存在服務器端緩存中,并返回對應的TOKEN到客戶端(該請求不需要進行簽名認證)

publicHttpResponseMessage GetToken(string staffId)

? ? ? ? {

? ? ? ? ? ? ResultMsg resultMsg =null;

? ? ? ? ? ? intid =0;

? ? ? ? ? ? //判斷參數是否合法if(string.IsNullOrEmpty(staffId) || (!int.TryParse(staffId,out id)))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;

? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();

? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? }

? ? ? ? ? ? //插入緩存Token token =(Token)HttpRuntime.Cache.Get(id.ToString());

? ? ? ? ? ? if(HttpRuntime.Cache.Get(id.ToString()) ==null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? token =new Token();

? ? ? ? ? ? ? ? token.StaffId = id;

? ? ? ? ? ? ? ? token.SignToken = Guid.NewGuid();

? ? ? ? ? ? ? ? token.ExpireTime = DateTime.Now.AddDays(1);

? ? ? ? ? ? ? ? HttpRuntime.Cache.Insert(token.StaffId.ToString(), token, null, token.ExpireTime, TimeSpan.Zero);

? ? ? ? ? ? }

? ? ? ? ? ? //返回token信息resultMsg =new ResultMsg();

? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.Success;

? ? ? ? ? ? resultMsg.Info ="";

? ? ? ? ? ? resultMsg.Data = token;

? ? ? ? ? ? return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? }


2.客戶端調用服務器端API,需要對請求進行簽名認證,簽名方式如下?

(1) get請求:按照請求參數名稱將所有請求參數按照字母先后順序排序得到:keyvaluekeyvalue...keyvalue??字符串如:將arong=1,mrong=2,crong=3?排序為:arong=1,?crong=3,mrong=2??然后將參數名和參數值進行拼接得到參數字符串:arong1crong3mrong2。?

publicstaticTuple GetQueryString(Dictionary parames)

? ? ? ? {

? ? ? ? ? ? // 第一步:把字典按Key的字母順序排序IDictionary sortedParams =newSortedDictionary(parames);

? ? ? ? ? ? IEnumerator> dem = sortedParams.GetEnumerator();

? ? ? ? ? ? // 第二步:把所有參數名和參數值串在一起StringBuilder query =newStringBuilder("");//簽名字符串StringBuilder queryStr =newStringBuilder("");//url參數if(parames ==null|| parames.Count ==0)

? ? ? ? ? ? ? ? returnnewTuple("","");

? ? ? ? ? ? while (dem.MoveNext())

? ? ? ? ? ? {

? ? ? ? ? ? ? ? stringkey = dem.Current.Key;

? ? ? ? ? ? ? ? stringvalue = dem.Current.Value;

? ? ? ? ? ? ? ? if(!string.IsNullOrEmpty(key))

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? query.Append(key).Append(value);

? ? ? ? ? ? ? ? ? ? queryStr.Append("&").Append(key).Append("=").Append(value);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? returnnewTuple(query.ToString(), queryStr.ToString().Substring(1, queryStr.Length -1));

? ? ? ? }


post請求:將請求的參數對象序列化為json格式字符串

Product product =newProduct() { Id =1, Name ="安慕希", Count =10, Price =58.8 };

var data=JsonConvert.SerializeObject(product);


(2)在請求頭中添加timespan(時間戳),nonce(隨機數),staffId(用戶Id),signature(簽名參數)

//加入頭信息request.Headers.Add("staffid", staffId.ToString());//當前請求用戶StaffIdrequest.Headers.Add("timestamp", timeStamp);//發起請求時的時間戳(單位:毫秒)request.Headers.Add("nonce", nonce);//發起請求時的時間戳(單位:毫秒)request.Headers.Add("signature", GetSignature(timeStamp,nonce,staffId,data));//當前請求內容的數字簽名


(3)根據請求參數計算本次請求的簽名,用timespan+nonc+staffId+token+data(請求參數字符串)得到signStr簽名字符串,然后再進行排序和MD5加密得到最終的signature簽名字符串,添加到請求頭中

privatestaticstringGetSignature(stringtimeStamp,stringnonce,intstaffId,string data)

? ? ? ? {

? ? ? ? ? ? Token token =null;

? ? ? ? ? ? varresultMsg = GetSignToken(staffId);

? ? ? ? ? ? if(resultMsg !=null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? if(resultMsg.StatusCode == (int)StatusCodeEnum.Success)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? token = resultMsg.Result;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? else? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? thrownew Exception(resultMsg.Data.ToString());

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? else? ? ? ? ? ? {

? ? ? ? ? ? ? ? thrownewException("token為null,員工編號為:"+staffId);

? ? ? ? ? ? }

? ? ? ? ? ? varhash = System.Security.Cryptography.MD5.Create();

? ? ? ? ? ? //拼接簽名數據varsignStr = timeStamp +nonce+ staffId + token.SignToken.ToString() + data;

? ? ? ? ? ? //將字符串中字符按升序排序varsortStr =string.Concat(signStr.OrderBy(c => c));

? ? ? ? ? ? varbytes = Encoding.UTF8.GetBytes(sortStr);

? ? ? ? ? ? //使用MD5加密varmd5Val = hash.ComputeHash(bytes);

? ? ? ? ? ? //把二進制轉化為大寫的十六進制StringBuilder result =new StringBuilder();

? ? ? ? ? ? foreach(varcin md5Val)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? result.Append(c.ToString("X2"));

? ? ? ? ? ? }

? ? ? ? ? ? return result.ToString().ToUpper();

? ? ? ? }


(4) webapi接收到相應的請求,取出請求頭中的timespan,nonc,staffid,signature 數據,根據timespan判斷此次請求是否失效,根據staffid取出相應token判斷token是否失效,根據請求類型取出對應的請求參數,然后服務器端按照同樣的規則重新計算請求簽名,判斷和請求頭中的signature數據是否相同,如果相同的話則是合法請求,正常返回數據,如果不相同的話,該請求可能被惡意篡改,禁止訪問相應的數據,返回相應的錯誤信息?

?如下使用全局過濾器攔截所有api請求進行統一的處理

publicclass ApiSecurityFilter : ActionFilterAttribute

? ? {

? ? ? ? publicoverridevoid OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)

? ? ? ? {

? ? ? ? ? ? ResultMsg resultMsg =null;

? ? ? ? ? ? varrequest = actionContext.Request;

? ? ? ? ? ? stringmethod = request.Method.Method;

? ? ? ? ? ? stringstaffid = String.Empty, timestamp =string.Empty, nonce =string.Empty, signature =string.Empty;

? ? ? ? ? ? intid =0;

? ? ? ? ? ? if(request.Headers.Contains("staffid"))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? staffid = HttpUtility.UrlDecode(request.Headers.GetValues("staffid").FirstOrDefault());

? ? ? ? ? ? }

? ? ? ? ? ? if(request.Headers.Contains("timestamp"))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());

? ? ? ? ? ? }

? ? ? ? ? ? if(request.Headers.Contains("nonce"))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());

? ? ? ? ? ? }

? ? ? ? ? ? if(request.Headers.Contains("signature"))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());

? ? ? ? ? ? }

? ? ? ? ? ? //GetToken方法不需要進行簽名驗證if(actionContext.ActionDescriptor.ActionName =="GetToken")

? ? ? ? ? ? {

? ? ? ? ? ? ? ? if(string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid,outid) ||string.IsNullOrEmpty(timestamp) ||string.IsNullOrEmpty(nonce)))

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;

? ? ? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();

? ? ? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? ? ? actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? else? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? //判斷請求頭是否包含以下參數if(string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid,outid) ||string.IsNullOrEmpty(timestamp) ||string.IsNullOrEmpty(nonce) ||string.IsNullOrEmpty(signature)))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;

? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();

? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? //判斷timespan是否有效doublets1 =0;

? ? ? ? ? ? doublets2 = (DateTime.UtcNow -newDateTime(1970,1,1,0,0,0,0)).TotalMilliseconds;

? ? ? ? ? ? booltimespanvalidate =double.TryParse(timestamp,out ts1);

? ? ? ? ? ? doublets = ts2 - ts1;

? ? ? ? ? ? boolfalg = ts >int.Parse(WebSettingsConfig.UrlExpireTime) *1000;

? ? ? ? ? ? if(falg || (!timespanvalidate))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.URLExpireError;

? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.URLExpireError.GetEnumText();

? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? //判斷token是否有效Token token = (Token)HttpRuntime.Cache.Get(id.ToString());

? ? ? ? ? ? stringsigntoken =string.Empty;

? ? ? ? ? ? if(HttpRuntime.Cache.Get(id.ToString()) ==null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.TokenInvalid;

? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.TokenInvalid.GetEnumText();

? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? else? ? ? ? ? ? {

? ? ? ? ? ? ? ? signtoken = token.SignToken.ToString();

? ? ? ? ? ? }

? ? ? ? ? ? //根據請求類型拼接參數NameValueCollection form = HttpContext.Current.Request.QueryString;

? ? ? ? ? ? stringdata =string.Empty;

? ? ? ? ? ? switch (method)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? case"POST":

? ? ? ? ? ? ? ? ? ? Stream stream = HttpContext.Current.Request.InputStream;

? ? ? ? ? ? ? ? ? ? stringresponseJson =string.Empty;

? ? ? ? ? ? ? ? ? ? StreamReader streamReader =new StreamReader(stream);

? ? ? ? ? ? ? ? ? ? data = streamReader.ReadToEnd();

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? case"GET":

? ? ? ? ? ? ? ? ? ? //第一步:取出所有get參數IDictionary parameters =newDictionary();

? ? ? ? ? ? ? ? ? ? for(intf =0; f < form.Count; f++)

? ? ? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? ? ? stringkey = form.Keys[f];

? ? ? ? ? ? ? ? ? ? ? ? parameters.Add(key, form[key]);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? // 第二步:把字典按Key的字母順序排序IDictionary sortedParams =newSortedDictionary(parameters);

? ? ? ? ? ? ? ? ? ? IEnumerator> dem = sortedParams.GetEnumerator();

? ? ? ? ? ? ? ? ? ? // 第三步:把所有參數名和參數值串在一起StringBuilder query =new StringBuilder();

? ? ? ? ? ? ? ? ? ? while (dem.MoveNext())

? ? ? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? ? ? stringkey = dem.Current.Key;

? ? ? ? ? ? ? ? ? ? ? ? stringvalue = dem.Current.Value;

? ? ? ? ? ? ? ? ? ? ? ? if(!string.IsNullOrEmpty(key))

? ? ? ? ? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? ? ? ? ? query.Append(key).Append(value);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? data = query.ToString();

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? default:

? ? ? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.HttpMehtodError;

? ? ? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.HttpMehtodError.GetEnumText();

? ? ? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? ? ? actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }


? ? ? ? ? ? boolresult = SignExtension.Validate(timestamp, nonce, id, signtoken,data, signature);

? ? ? ? ? ? if(!result)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? resultMsg =new ResultMsg();

? ? ? ? ? ? ? ? resultMsg.StatusCode = (int)StatusCodeEnum.HttpRequestError;

? ? ? ? ? ? ? ? resultMsg.Info = StatusCodeEnum.HttpRequestError.GetEnumText();

? ? ? ? ? ? ? ? resultMsg.Data ="";

? ? ? ? ? ? ? ? actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));

? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? else? ? ? ? ? ? {

? ? ? ? ? ? ? ? base.OnActionExecuting(actionContext);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? publicoverridevoid OnActionExecuted(HttpActionExecutedContext actionExecutedContext)

? ? ? ? {

? ? ? ? ? ? base.OnActionExecuted(actionExecutedContext);? ? ? ? }? ? }


然后我們進行測試,檢驗api請求的合法性

Get請求:

1.獲取產品數據,傳遞參數id=1,name="wahaha" ?,完整請求為http://localhost:14826/api/product/getproduct?id=1&name=wahaha


2.請求頭添加timespan,staffid,nonce,signature字段


?3.如圖當data里面的值為id1namewahaha的時候請求頭中的signature和服務器端計算出來的result的值是完全一樣的,當我將data修改為id1namewahaha1之后,服務器端計算出來的簽名result和請求頭中提交的signature就不相同了,就表示為不合法的請求了


4.不合法的請求就會被識別為請求參數已被修改

? 合法的請求則會返回對應的商品信息

post請求:

1.post對象序列化為json字符串后提交到后臺,后臺返回相應產品信息


2.后臺獲取請求的參數信息

3.判斷簽名是否成功,第一次請求簽名參數signature和服務器端計算result完全相同, 然后當把請求參數中count的數量從10改成100之后服務器端計算的result和請求簽名參數signature不同,所以請求不合法,是非法請求,同理如果其他任何參數被修改最后計算的結果都會和簽名參數不同,請求同樣識別為不合法請求


總結:

通過上面的案例,我們可以看出,安全的關鍵在于參與簽名的TOKEN,整個過程中TOKEN是不參與通信的,所以只要保證TOKEN不泄露,請求就不會被偽造。

然后我們通過timestamp時間戳用來驗證請求是否過期,這樣就算被人拿走完整的請求鏈接也是無效

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • https://www.cnblogs.com/MR-YY/archive/2016/10/18/5972380....
    suzheya閱讀 1,781評論 0 0
  • 登錄的業務邏輯 { http:是短連接. 服務器如何判斷當前用戶是否登錄? // 1. 如果是即時通信類:長連接....
    VincentHK閱讀 2,274評論 0 9
  • 我們的曾經只剩下回憶, 我們的現在只剩下爭吵, 我們的未來只剩下黑暗。 所以我決定給你自由, 所以我決定獨自離開,...
    汐菲兒閱讀 183評論 0 0
  • 燕子去了,有再來的時候;楊柳枯了,有再青的時候;桃花謝了,有再開的時候。但是,聰明的,你告訴我,我們的日子為什么一...
    魚小哆閱讀 3,388評論 18 59