微服務(wù)的用戶認(rèn)證與授權(quán)雜談

[TOC]


有狀態(tài) VS 無狀態(tài)

幾乎絕大部分的應(yīng)用都需要實現(xiàn)認(rèn)證與授權(quán)臼寄,例如用戶使用賬戶密碼登錄就是一個認(rèn)證過程孵淘,認(rèn)證登錄成功后系統(tǒng)才會允許用戶訪問其賬戶下的相關(guān)資源,這就是所謂的授權(quán)。而復(fù)雜點的情況就是用戶會有角色概念,每個角色所擁有的權(quán)限不同,給用戶賦予某個角色的過程也是一個授權(quán)過程棵逊。

用戶的登錄態(tài)在服務(wù)器端分為有狀態(tài)和無狀態(tài)兩種模式伤疙,在單體分布式架構(gòu)的時代,我們?yōu)榱四茏孲ession信息在多個Tomcat實例之間共享辆影,通常的解決方案是將Session存儲至一個緩存數(shù)據(jù)庫中徒像。即下圖中的Session Store,這個Session Store可以是Redis也可以是MemCache蛙讥,這種模式就是有狀態(tài)的:


image.png

之所以說是有狀態(tài)锯蛀,是因為服務(wù)端需要維護、存儲這個Session信息次慢,即用戶的登錄態(tài)實際是在服務(wù)端維護的旁涤,所以對服務(wù)端來說可以隨時得知用戶的登錄態(tài)翔曲,并且對用戶的Session有比較高的控制權(quán)。有狀態(tài)模式的缺點主要是在于這個Session Store上劈愚,如果作為Session Store的服務(wù)只有一個節(jié)點的話瞳遍,當(dāng)業(yè)務(wù)擴展、用戶量增多時就會有性能瓶頸問題菌羽,而且數(shù)據(jù)遷移也比較麻煩掠械。當(dāng)然也可以選擇去增加節(jié)點,只不過就需要投入相應(yīng)的機器成本了注祖。

另一種無狀態(tài)模式猾蒂,指的是服務(wù)器端不去記錄用戶的登錄狀態(tài),也就是服務(wù)器端不再去維護一個Session是晨。而是在用戶登錄成功的時候肚菠,頒發(fā)一個token給客戶端,之后客戶端的每個請求都需要攜帶token署鸡。服務(wù)端會對客戶端請求時所攜帶的token進行解密案糙,校驗token是否合法以及是否已過期等等。token校驗成功后則認(rèn)為用戶是具有登錄態(tài)的靴庆,否則認(rèn)為用戶未登錄:


image.png

注:token通常會存儲用戶的唯一ID时捌,解密token就是為了獲取用戶ID然后去緩存或者數(shù)據(jù)庫中查詢用戶數(shù)據(jù)。當(dāng)然也可以選擇將用戶數(shù)據(jù)都保存在token中炉抒,只不過這種方式可能會有安全問題或數(shù)據(jù)一致性問題

無狀態(tài)模式下的token其實和有狀態(tài)模式下的session作用是類似的奢讨,都是判斷用戶是否具有登錄態(tài)的一個憑證。只不過在無狀態(tài)模式下焰薄,服務(wù)器端不需要再去維護拿诸、存儲一個Session,只需要對客戶端攜帶的token進行解密和校驗塞茅。也就是說存儲實際是交給了客戶端完成亩码,所以無狀態(tài)的優(yōu)點恰恰就是彌補了有狀態(tài)的缺點。但是無狀態(tài)的缺點也很明顯野瘦,因為一旦把token交給客戶端后描沟,服務(wù)端就無法去控制這個token了。例如想要強制下線某個用戶在無狀態(tài)的模式下就比較難以實現(xiàn)鞭光。

有狀態(tài)與無狀態(tài)各有優(yōu)缺點吏廉,只不過目前業(yè)界趨勢更傾向于無狀態(tài):

優(yōu)缺點 有狀態(tài) 無狀態(tài)
優(yōu)點 服務(wù)端控制能力強 去中心化,無存儲惰许,簡單拔第,任意擴容访递、縮容
缺點 存在中心點诵姜,雞蛋放在一個籃子里,遷移麻煩聊倔。服務(wù)端存儲數(shù)據(jù),加大了服務(wù)端壓力 服務(wù)端控制能力相對弱

微服務(wù)認(rèn)證方案

微服務(wù)認(rèn)證方案有很多種畦戒,需要根據(jù)實際的業(yè)務(wù)需求定制適合自己業(yè)務(wù)的方案方库,這里簡單列舉一下業(yè)界內(nèi)常用的微服務(wù)認(rèn)證方案。

1障斋、“處處安全” 方案:

所謂“處處安全” 方案纵潦,就是考慮了微服務(wù)認(rèn)證中的方方面面,這種方案主流是使用OAuth2協(xié)議進行實現(xiàn)垃环。這種方案的優(yōu)點是安全性好邀层,但是實現(xiàn)的成本及復(fù)雜性比較高。另外遂庄,多個微服務(wù)之間互相調(diào)用需要傳遞token寥院,所以會發(fā)生多次認(rèn)證,有一定的性能開銷

OAuth2的代表實現(xiàn)框架:

參考文章:

2涛目、外部無狀態(tài)秸谢,內(nèi)部有狀態(tài)方案:

這種方案雖然看著有些奇葩,但是也許多公司在使用霹肝。在該方案下估蹄,網(wǎng)關(guān)不存儲Session,而是接收一個token和JSESSIONID沫换,網(wǎng)關(guān)僅對token進行解密臭蚁、校驗,然后將JSESSIONID轉(zhuǎn)發(fā)到其代理的微服務(wù)上讯赏,這些微服務(wù)則是通過JSESSIONID從Session Store獲取共享Session垮兑。如下圖:


image.png

這種方案主要是出現(xiàn)在內(nèi)部有舊的系統(tǒng)架構(gòu)的情況,在不重構(gòu)或者沒法全部重構(gòu)的前提下為了兼容舊的系統(tǒng)漱挎,就可以采用該方案系枪。而且也可以將新舊系統(tǒng)分為兩塊,網(wǎng)關(guān)將token和JSESSIONID一并轉(zhuǎn)發(fā)到下游服務(wù)磕谅,這樣無狀態(tài)模式的系統(tǒng)則使用token私爷,有狀態(tài)模式的系統(tǒng)則使用Session,然后再慢慢地將舊服務(wù)進行重構(gòu)以此實現(xiàn)一個平滑過渡怜庸。如下圖:


image.png

3当犯、“網(wǎng)關(guān)認(rèn)證授權(quán)垢村,內(nèi)部裸奔” 方案:

在該方案下割疾,認(rèn)證授權(quán)在網(wǎng)關(guān)完成,下游的微服務(wù)不需要進行認(rèn)證授權(quán)嘉栓。網(wǎng)關(guān)接收到客戶端請求所攜帶的token后宏榕,對該token進行解密和校驗拓诸,然后將解密出來的用戶信息轉(zhuǎn)發(fā)給下游微服務(wù)。這種方案的優(yōu)點是實現(xiàn)簡單麻昼、性能也好奠支,缺點是一旦網(wǎng)關(guān)被攻破,或者能越過網(wǎng)關(guān)訪問微服務(wù)就會有安全問題抚芦。如下圖:


image.png

4倍谜、“內(nèi)部裸奔” 改進方案:

上一個方案的缺陷比較明顯,我們可以對該方案進行一些改進叉抡,例如引入一個認(rèn)證授權(quán)中心服務(wù)尔崔,讓網(wǎng)關(guān)不再做認(rèn)證和授權(quán)以及token的解密和解析。用戶的登錄請求通過網(wǎng)關(guān)轉(zhuǎn)發(fā)到認(rèn)證授權(quán)中心完成登錄褥民,登錄成功后由認(rèn)證授權(quán)中心頒發(fā)token給客戶端季春。客戶端每次請求都攜帶token消返,而每個微服務(wù)都需要對token進行解密和解析载弄,以確定用戶的登錄態(tài)。改進之后所帶來的好處就是網(wǎng)關(guān)不再關(guān)注業(yè)務(wù)撵颊,而是單純的請求做轉(zhuǎn)發(fā)宇攻,可以在一定程度上解耦業(yè)務(wù),并且也更加安全秦驯,因為每個微服務(wù)不再裸奔而是都需要驗證請求中所攜帶的token尺碰。如下圖:


image.png

5、方案的對比與選擇:

以上所提到的常見方案只是用于拋磚引玉译隘,沒有哪個方案是絕對普適的亲桥。而且實際開發(fā)中通常會根據(jù)業(yè)務(wù)改進、組合這些方案演變出不同的變種固耘,所以應(yīng)該要學(xué)會活學(xué)活用而不是局限于某一種方案题篷。下面簡單整理了一下這幾種方案,以便做對比:


image.png

6厅目、訪問控制模型

了解了常見的微服務(wù)認(rèn)證方案后番枚,我們來簡單看下訪問控制模型。所謂訪問控制损敷,就是用戶需要滿足怎么樣的條件才允許訪問某個系統(tǒng)資源葫笼,即控制系統(tǒng)資源的訪問權(quán)限。訪問控制模型主要有以下幾種:

  1. Access Control List(ACL拗馒,訪問控制列表):

在該模型下的一個系統(tǒng)資源會包含一組權(quán)限列表路星,該列表規(guī)定了哪些用戶擁有哪些操作權(quán)限。例如有一個系統(tǒng)資源包含的權(quán)限列表為:[Alice: read, write; Bob: read]诱桂;那么就表示Alice這個用戶對該資源擁有read和write權(quán)限洋丐,而Bob這個用戶則對該資源擁有read權(quán)限呈昔。該模型通常用于文件系統(tǒng)

  1. Role-based access control(RBAC,基于角色的訪問控制):

即用戶需關(guān)聯(lián)一個預(yù)先定義的角色友绝,而不同的角色擁有各自的權(quán)限列表堤尾。用戶登錄后只需要查詢其關(guān)聯(lián)的角色就能查出該用戶擁有哪些權(quán)限。例如用戶A關(guān)聯(lián)了一個名為觀察者的角色迁客,該角色下包含接口A和接口B的訪問權(quán)限郭宝,那么就表示用戶A僅能夠訪問A和接口B。該模型在業(yè)務(wù)系統(tǒng)中使用得最多

  1. Attribute-based access control(ABAC掷漱,基于屬性的訪問控制):

在該模型下剩蟀,用戶在訪問某個系統(tǒng)資源時會攜帶一組屬性值包括自身屬性、主題屬性切威、資源屬性以及環(huán)境屬性等育特。然后系統(tǒng)通過動態(tài)計算用戶所攜帶的屬性來判斷是否滿足具有訪問某個資源的權(quán)限。屬性通常來說分為四類:用戶屬性(如用戶年齡)先朦,環(huán)境屬性(如當(dāng)前時間)缰冤,操作屬性(如讀取)以及對象屬性等喳魏。

為了能讓系統(tǒng)進行權(quán)限控制棉浸,在該模型下需要以特定的格式定義權(quán)限規(guī)則,例如:IF 用戶是管理員; THEN 允許對敏感數(shù)據(jù)進行讀/寫操作刺彩。在這條規(guī)則中“管理員”是用戶的角色屬性迷郑,而“讀/寫”是操作屬性,”敏感數(shù)據(jù)“則是對象屬性创倔。

ABAC有時也被稱為PBAC(Policy-Based Access Control嗡害,基于策略的訪問控制)或CBAC(Claims-Based Access Control,基于聲明的訪問控制)畦攘。該模型由于比較復(fù)雜霸妹,使用得不多,k8s也因為ABAC太復(fù)雜而在1.8版本改為使用RBAC

  1. Rules-based access control(RBAC知押,基于規(guī)則的訪問控制):

在該模型下通過對某個系統(tǒng)資源事先定義一組訪問規(guī)則來實現(xiàn)訪問控制叹螟,這些規(guī)則可以是參數(shù)、時間台盯、用戶信息等罢绽。例如:只允許從特定的IP地址訪問或拒絕從特定的IP地址訪問

  1. Time-based access control list(TBACL,基于時間的訪問控制列表):

該模型是在ACL的基礎(chǔ)上添加了時間的概念静盅,可以設(shè)置ACL權(quán)限在特定的時間才生效良价。例如:只允許某個系統(tǒng)資源在工作日時間內(nèi)才能被外部訪問,那么就可以將該資源的ACL權(quán)限的有效時間設(shè)置為工作日時間內(nèi)


JWT

之前提到過無狀態(tài)模式下,服務(wù)器端需要生成一個Token頒發(fā)給客戶端棚壁,而目前主流的方式就是使用JWT的標(biāo)準(zhǔn)來生成Token,所以本小節(jié)我們來簡單了解下JWT及其使用栈虚。

JWT簡介:

JWT是JSON Web Token的縮寫袖外,JWT實際是一個開放標(biāo)準(zhǔn)(RFC 7519),用來在各方之間安全地傳輸信息魂务,是目前最流行的跨域認(rèn)證解決方案曼验。JWT可以被驗證和信任,因為它是數(shù)字簽名的粘姜。官網(wǎng):https://jwt.io/

JWT的組成結(jié)構(gòu):

組成 作用 內(nèi)容示例
Header(頭) 記錄Token類型鬓照、簽名的算法等 {"alg": "HS256", "type": "JWT"}
Payload(有效載荷) 攜帶一些用戶信息及Token的過期時間等 {"user_id": "1", "iat": 1566284273, "exp": 1567493873}
Signature(簽名) 簽名算法生成的數(shù)字簽名,用于防止Token被篡改孤紧、確保Token的安全性 WV5Hhymti3OgIjPprLJKJv3aY473vyxMLeM8c7JLxSk

JWT生成Token的公式:

Token = Base64(Header).Base64(Payload).Base64(Signature)

示例:eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjYyODIyMjMsImV4cCI6MTU2NzQ5MTgyM30.OtCOFqWMS6ZOzmwCs7NC7hs9u043P-09KbQfZBov97E

簽名是使用Header里指定的簽名算法生成的豺裆,公式如下:

Signature = 簽名算法((Base64(Header).Base64(Payload), 秘鑰))


使用JWT:

1、目前Java語言有好幾個操作JWT的第三方庫号显,這里采用其中較為輕巧的jjwt作為演示臭猜。首先添加依賴如下:

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.10.7</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.10.7</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId>
  <version>0.10.7</version>
  <scope>runtime</scope>
</dependency>

2、編寫一個工具類押蚤,將JWT的操作都抽取出來蔑歌,方便在項目中的使用。具體代碼如下:

package com.zj.node.usercenter.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

/**
 * JWT 工具類
 *
 * @author 01
 * @date 2019-08-20
 **/
@Slf4j
@Component
@RequiredArgsConstructor
@SuppressWarnings("WeakerAccess")
public class JwtOperator {
    /**
     * 秘鑰
     * - 默認(rèn)5d1IB9SiWd5tjBx&EMi^031CtigL!6jJ
     */
    @Value("${jwt.secret:5d1IB9SiWd5tjBx&EMi^031CtigL!6jJ}")
    private String secret;
    /**
     * 有效期揽碘,單位秒
     * - 默認(rèn)2周
     */
    @Value("${jwt.expire-time-in-second:1209600}")
    private Long expirationTimeInSecond;

    /**
     * 從token中獲取claim
     *
     * @param token token
     * @return claim
     */
    public Claims getClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(this.secret.getBytes())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException |
                MalformedJwtException | IllegalArgumentException e) {
            log.error("token解析錯誤", e);
            throw new IllegalArgumentException("Token invalided.");
        }
    }

    /**
     * 獲取token的過期時間
     *
     * @param token token
     * @return 過期時間
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token)
                .getExpiration();
    }

    /**
     * 判斷token是否過期
     *
     * @param token token
     * @return 已過期返回true次屠,未過期返回false
     */
    private Boolean isTokenExpired(String token) {
        Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 計算token的過期時間
     *
     * @return 過期時間
     */
    private Date getExpirationTime() {
        return new Date(System.currentTimeMillis() + this.expirationTimeInSecond * 1000);
    }

    /**
     * 為指定用戶生成token
     *
     * @param claims 用戶信息
     * @return token
     */
    public String generateToken(Map<String, Object> claims) {
        Date createdTime = new Date();
        Date expirationTime = this.getExpirationTime();


        byte[] keyBytes = secret.getBytes();
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdTime)
                .setExpiration(expirationTime)
                // 你也可以改用你喜歡的算法
                // 支持的算法詳見:https://github.com/jwtk/jjwt#features
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 判斷token是否非法
     *
     * @param token token
     * @return 未過期返回true,否則返回false
     */
    public Boolean validateToken(String token) {
        return !isTokenExpired(token);
    }
}

3雳刺、若默認(rèn)的配置不符合需求劫灶,可以通過在配置文件中添加如下配置進行自定義:

jwt:
  # 秘鑰
  secret: 5d1IB9SiWd5tjBx&EMi^031CtigL!6jJ
  # jwt有效期,單位秒
  expire-time-in-second: 1209600

4掖桦、完成以上步驟后浑此,就可以在項目中使用JWT了,這里提供了一個比較全面的測試用例滞详,可以參考測試用例來使用該工具類凛俱。代碼如下:

package com.zj.node.usercenter.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.security.SignatureException;
import org.apache.tomcat.util.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

/**
 * JwtOperator 測試用例
 *
 * @author 01
 * @date 2019-08-20
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class JwtOperatorTests {

    @Autowired
    private JwtOperator jwtOperator;

    private String token = "";

    @Before
    public void generateTokenTest() {
        // 設(shè)置用戶信息
        Map<String, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("id", "1");

        // 測試1: 生成token
        this.token = jwtOperator.generateToken(objectObjectHashMap);
        // 會生成類似該字符串的內(nèi)容: eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk4MTcsImV4cCI6MTU2Njc5OTQxN30.27_QgdtTg4SUgxidW6ALHFsZPgMtjCQ4ZYTRmZroKCQ
        System.out.println(this.token);
    }

    @Test
    public void validateTokenTest() {
        // 測試2: 如果能token合法且未過期,返回true
        Boolean validateToken = jwtOperator.validateToken(this.token);
        System.out.println("token校驗結(jié)果:" + validateToken);
    }

    @Test
    public void getClaimsFromTokenTest() {
        // 測試3: 解密token料饥,獲取用戶信息
        Claims claims = jwtOperator.getClaimsFromToken(this.token);
        System.out.println(claims);
    }

    @Test
    public void decodeHeaderTest() {
        // 獲取Header蒲犬,即token的第一段(以.為邊界)
        String[] split = this.token.split("\\.");
        String encodedHeader = split[0];

        // 測試4: 解密Header
        byte[] header = Base64.decodeBase64(encodedHeader.getBytes());
        System.out.println(new String(header));
    }

    @Test
    public void decodePayloadTest() {
        // 獲取Payload,即token的第二段(以.為邊界)
        String[] split = this.token.split("\\.");
        String encodedPayload = split[1];

        // 測試5: 解密Payload
        byte[] payload = Base64.decodeBase64(encodedPayload.getBytes());
        System.out.println(new String(payload));
    }

    @Test(expected = SignatureException.class)
    public void validateErrorTokenTest() {
        try {
            // 測試6: 篡改原本的token岸啡,因此會報異常原叮,說明JWT是安全的
            jwtOperator.validateToken(this.token + "xx");
        } catch (SignatureException e) {
            e.printStackTrace();
            throw e;
        }
    }
}

若希望了解各類的JWT庫,可以參考如下文章:


使用JWT實現(xiàn)認(rèn)證授權(quán)

了解了JWT后,我們來使用JWT實現(xiàn)一個認(rèn)證授權(quán)Demo奋隶,首先定義一個DTO擂送,其結(jié)構(gòu)如下:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRespDTO {
    /**
     * 昵稱
     */
    private String userName;

    /**
     * token
     */
    private String token;

    /**
     * 過期時間
     */
    private Long expirationTime;
}

然后編寫Service,提供模擬登錄和模擬檢查用戶登錄態(tài)的方法唯欣。具體代碼如下:

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final JwtOperator jwtOperator;

    /**
     * 模擬用戶登錄
     */
    public LoginRespDTO login(String userName, String password) {
        String defPassword = "123456";
        if (!defPassword.equals(password)) {
            return null;
        }

        // 密碼驗證通過頒發(fā)token
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("userName", userName);
        String token = jwtOperator.generateToken(userInfo);

        return LoginRespDTO.builder()
                .userName(userName)
                .token(token)
                .expirationTime(jwtOperator.getExpirationDateFromToken(token).getTime())
                .build();
    }

    /**
     * 模擬登錄態(tài)驗證
     */
    public String checkLoginState(String token) {
        if (jwtOperator.validateToken(token)) {
            Claims claims = jwtOperator.getClaimsFromToken(token);
            String userName = claims.get("userName").toString();

            return String.format("用戶 %s 的登錄態(tài)驗證通過嘹吨,允許訪問", userName);
        }

        return "登錄態(tài)驗證失敗,token無效或過期";
    }
}

接著是Controller層境氢,開放相應(yīng)的Web接口蟀拷。代碼如下:

@Slf4j
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/login")
    public LoginRespDTO login(@RequestParam("userName") String userName,
                              @RequestParam("password") String password) {
        return userService.login(userName, password);
    }

    @GetMapping("/checkLoginState")
    public String checkLoginState(@RequestParam("token") String token) {
        return userService.checkLoginState(token);
    }
}

用戶登錄成功,返回Token和用戶基本信息:


image.png

校驗登錄態(tài):


image.png

Tips:

本小節(jié)只是給出了一個極簡的例子萍聊,目的是演示如何使用JWT實現(xiàn)用戶登錄成功后頒發(fā)Token給客戶端以及通過Token驗證用戶的登錄態(tài)问芬,這樣大家完全可以通過之前提到過的方案進行拓展。通常來說Token頒發(fā)給客戶端后寿桨,客戶端在后續(xù)的請求中是將Token放在HTTP Header里進行傳遞的此衅,而不是示例中的參數(shù)傳遞。微服務(wù)之間的Token傳遞也是如此亭螟,一個微服務(wù)在向另一個微服務(wù)發(fā)請求之前炕柔,需要先將Token放進本次請求的HTTP Header里。另外媒佣,驗證Token的邏輯一般是放在一個全局的過濾器或者攔截器中匕累,這樣就不需要每個接口都寫一遍驗證邏輯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末默伍,一起剝皮案震驚了整個濱河市欢嘿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌也糊,老刑警劉巖炼蹦,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異狸剃,居然都是意外死亡掐隐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門钞馁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虑省,“玉大人,你說我怎么就攤上這事僧凰√骄保” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵训措,是天一觀的道長伪节。 經(jīng)常有香客問我光羞,道長,這世上最難降的妖魔是什么怀大? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任纱兑,我火速辦了婚禮,結(jié)果婚禮上化借,老公的妹妹穿的比我還像新娘潜慎。我一直安慰自己,他們只是感情好屏鳍,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著局服,像睡著了一般钓瞭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淫奔,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天山涡,我揣著相機與錄音,去河邊找鬼唆迁。 笑死鸭丛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的唐责。 我是一名探鬼主播鳞溉,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鼠哥!你這毒婦竟也來了熟菲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朴恳,失蹤者是張志新(化名)和其女友劉穎抄罕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體于颖,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡呆贿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了森渐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片做入。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖同衣,靈堂內(nèi)的尸體忽然破棺而出母蛛,到底是詐尸還是另有隱情,我是刑警寧澤乳怎,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布彩郊,位于F島的核電站前弯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秫逝。R本人自食惡果不足惜恕出,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望违帆。 院中可真熱鬧浙巫,春花似錦、人聲如沸刷后。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尝胆。三九已至丧裁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間含衔,已是汗流浹背煎娇。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贪染,地道東北人缓呛。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像杭隙,于是被迫代替她去往敵國和親哟绊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容