Spring Boot整合Shiro和JWT的無狀態(tài)權(quán)限管理方案

本文涉及的源碼地址:https://github.com/davidfantasy/shrio-with-jwt-spring-boot-starter

背景說明

用戶權(quán)限管理是每個(gè)信息系統(tǒng)最基本的需求枫慷,對基于Java的項(xiàng)目來說渊迁,最常用的權(quán)限管理框架就是大名鼎鼎的Apache Shiro。Apache Shiro功能非常強(qiáng)大亚皂,使用廣泛,幾乎成為了權(quán)限管理的代名詞甥雕。但對于普通項(xiàng)目來說剩晴,Shiro的設(shè)計(jì)理念因?yàn)樽非箪`活性,一些概念如Realm几迄,Subject的抽象級別都比較高,顯得比較復(fù)雜冰评。如果沒有對框架細(xì)節(jié)進(jìn)行深入了解的話映胁,很難理解其中的準(zhǔn)確含義。要將其應(yīng)用于實(shí)際項(xiàng)目甲雅,還需要針對項(xiàng)目的實(shí)際情況做大量的配置和改造解孙,時(shí)間成本較高。

而且Shiro興起的時(shí)代主流應(yīng)用還是傳統(tǒng)的基于Session的Web網(wǎng)站抛人,并沒有過多的考慮目前流行的微服務(wù)等應(yīng)用形式的權(quán)限管理需求弛姜。導(dǎo)致其并沒有提供一套無狀態(tài)微服務(wù)的開箱即用的整合方案。需要在項(xiàng)目層面對Shiro進(jìn)行二次封裝和改進(jìn)妖枚,開發(fā)難度較大廷臼。

我負(fù)責(zé)的幾個(gè)項(xiàng)目都使用了Shiro作為權(quán)限管理框架,感嘆其強(qiáng)大功能的也為每次都需要進(jìn)行二次開發(fā)和封裝感到厭煩了,于是在對Shiro的結(jié)構(gòu)有比較深入的了解之后荠商,決定在Shrio的基礎(chǔ)上寂恬,對一些常用的開發(fā)場景進(jìn)行封裝和整合,提高開發(fā)效率结啼,降低配置難度掠剑,開發(fā)一套基于Spring Boot環(huán)境,適合于各類無狀態(tài)微服務(wù)應(yīng)用的郊愧,開箱即用的輕量級權(quán)限框架。

使用Aceess Token替換Session

所謂的無狀態(tài)井佑,其實(shí)是把原來由后端服務(wù)負(fù)責(zé)維護(hù)的属铁,基于Http Session的用戶會話信息交由客戶端(如果是普通的web應(yīng)用,客戶端即是用戶的瀏覽器)進(jìn)行維護(hù)躬翁,這樣后端服務(wù)的單元測試焦蘑,負(fù)載均衡,橫向擴(kuò)容都要方便很多盒发。

但是用戶會話信息關(guān)乎數(shù)據(jù)安全例嘱,放到客戶端如何確保安全呢?常見的做法是由服務(wù)端根據(jù)客戶端首次提交的認(rèn)證信息簽發(fā)一個(gè)accessToken宁舰,這個(gè)accessToken就相當(dāng)于客戶端的身份證拼卵,以后每次交互的時(shí)候客戶端只需要出示這個(gè)憑證,服務(wù)端就能夠識別當(dāng)前客戶端的身份蛮艰。

實(shí)現(xiàn)accessToken的方式有很多腋腮,理論上只要確保一個(gè)accessToken無法被第三方解碼,能唯一標(biāo)識一個(gè)客戶端壤蚜,服務(wù)端能夠解析出token的創(chuàng)建時(shí)間即寡,客戶端標(biāo)識等內(nèi)容就行了。但是自行設(shè)計(jì)的實(shí)現(xiàn)方法難免存在各種安全隱患袜刷,accessToken是要由客戶端進(jìn)行維護(hù)的聪富,我們無法確保客戶端都一定運(yùn)行在完全安全的環(huán)境中著蟹。幸運(yùn)的是現(xiàn)在有一種專門為此目的而設(shè)計(jì)的開放標(biāo)準(zhǔn)JWT(JSON Web token),它基于http交互中常見的數(shù)據(jù)格式JSON墩蔓,提供緊湊而安全的Token生成處理機(jī)制。JWT的詳細(xì)內(nèi)容這里就不多介紹了草则,有興趣可以自行查閱相關(guān)資料钢拧。

認(rèn)證和授權(quán)

Shiro默認(rèn)提供的實(shí)現(xiàn)是基于用戶Session的權(quán)限驗(yàn)證模型,如何讓其支持基于accessToken的無狀態(tài)形式呢炕横?Shiro功能的核心其實(shí)主要包含兩部分內(nèi)容:認(rèn)證(Authentication)和授權(quán)(Authorization)源内。認(rèn)證就是核實(shí)用戶身份的過程,比如檢查客戶端提供的用戶名和密碼是否是合法的系統(tǒng)用戶。而授權(quán)的含義則是檢查該用戶是否能夠訪問具體的某個(gè)資源膜钓,也就是訪問控制嗽交。所以我們需要做的就是擴(kuò)展Shiro對于認(rèn)證和授權(quán)的默認(rèn)實(shí)現(xiàn),使其能夠支持accessToken的形式颂斜。

我們先來看一下Shiro實(shí)現(xiàn)認(rèn)證的流程:


Shiro認(rèn)證流程圖.jpg

從整個(gè)流程圖上可以看出夫壁,最終實(shí)現(xiàn)認(rèn)證邏輯的組件是所謂的Realm,Shiro默認(rèn)實(shí)現(xiàn)了很多不同的Realm沃疮,可以從數(shù)據(jù)庫盒让,LADP等各個(gè)地方加載用戶的認(rèn)證信息。

授權(quán)過程和認(rèn)證過程差不多司蔬,核心也在于Realm的實(shí)現(xiàn):


Shiro授權(quán)流程圖.jpg

所以邑茄,第一步要做的應(yīng)該是先實(shí)現(xiàn)一個(gè)自定義的JWTShiroRealm,采用accessToken的方式來實(shí)現(xiàn)系統(tǒng)的認(rèn)證和授權(quán)俊啼。

public class JWTShiroRealm extends AuthorizingRealm {

   /**
     * 可供擴(kuò)展的權(quán)限加載器肺缕,由應(yīng)用程序負(fù)責(zé)實(shí)現(xiàn)
     */
    private JWTUserAuthService userAuthService;

    private JWTHelper jwtHelper;

    public JWTShiroRealm(JWTUserAuthService userAuthService, JWTHelper jwtHelper) {
        this.jwtHelper = jwtHelper;
        this.userAuthService = userAuthService;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 用于獲取用戶權(quán)限(role,permissions),只有當(dāng)需要檢測用戶權(quán)限的時(shí)候才會調(diào)用此方法,例如checkRole,checkPermission之類的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        JWTPrincipal principal = (JWTPrincipal) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
        UserInfo up = userAuthService.getUserInfo(principal.getAccount());
        if (up != null && up.getPermissions() != null) {
            authInfo.addStringPermissions(up.getPermissions());
        }
        return authInfo;
    }

    /**
     * 調(diào)用subject.login時(shí)觸發(fā)此方法授帕,用于驗(yàn)證token的正確性
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        // 從token中獲取用戶的唯一標(biāo)識
        String account = jwtHelper.getAccount(token);
        if (account== null) {
            throw new AuthenticationException("無效的請求");
        }
        UserInfo user = userAuthService.getUserInfo(account);
        if (user == null) {
            throw new AuthenticationException("未找到用戶信息");
        }
        DecodedJWT jwt = jwtHelper.verify(token, account, user.getSecret());
        if (jwt == null) {
            throw new AuthenticationException("token已經(jīng)過期同木,請重新登錄");
        }
        JWTPrincipal principal = new JWTPrincipal();
        principal.setAccount(user.getAccount());
        principal.setExpiresAt(jwt.getExpiresAt().getTime());
        //這里實(shí)際上會將AuthenticationToken.getCredentials()與傳入的第二個(gè)參數(shù)credentials進(jìn)行比較
        //第一個(gè)參數(shù)是登錄成功后,可以通過subject.getPrincipal獲取
        return new SimpleAuthenticationInfo(principal, token, this.getName());
    }

Realm核心的方法在于doGetAuthorizationInfodoGetAuthenticationInfo跛十,對應(yīng)之前說的授權(quán)和認(rèn)證過程彤路。這里需要注意的地方是,解析了accessToken之后應(yīng)該如何獲得用戶的認(rèn)證和權(quán)限等信息呢偶器?我的想法是斩萌,認(rèn)證方式和獲取用戶權(quán)限每個(gè)應(yīng)用系統(tǒng)都可能有不同的需求,沒辦法強(qiáng)行統(tǒng)一起來屏轰,所以這里應(yīng)該預(yù)留一個(gè)擴(kuò)展點(diǎn)颊郎。JWTShiroRealm只負(fù)責(zé)對accessToken的有效性進(jìn)行認(rèn)證,而把該Token是否對應(yīng)一個(gè)合法的用戶以及用戶的具體權(quán)限委派給具體的應(yīng)用去處理和實(shí)現(xiàn)霎苗。 而JWTUserAuthService就是這樣一個(gè)擴(kuò)展點(diǎn)姆吭,它被定義成一個(gè)接口,負(fù)責(zé)根據(jù)accessToken中定義的唯一標(biāo)示(一般就是用戶賬號)判斷用戶是否合法唁盏,以及通過用戶的唯一標(biāo)示加載該用戶的實(shí)際權(quán)限内狸,另外還可以自定義驗(yàn)證失敗時(shí)的錯誤返回。每個(gè)應(yīng)用程序需要根據(jù)業(yè)務(wù)特點(diǎn)實(shí)現(xiàn)自己的邏輯厘擂。

public interface JWTUserAuthService {

    /**
     * 根據(jù)用戶的唯一標(biāo)示對用戶進(jìn)行認(rèn)證昆淡,并獲取用戶的權(quán)限等信息
     * 如果account對應(yīng)的用戶信息不存在,應(yīng)返回null
     * @param account 用戶的唯一標(biāo)示
     * @return 該用戶所擁有的權(quán)限信息
     */
    UserInfo getUserInfo(String account);

    /**
     * 自定義訪問資源認(rèn)證失敗時(shí)的處理方式刽严,例如返回json格式的錯誤信息
     * {\"code\":401,\"message\":\"用戶認(rèn)證失敯毫椤!\")
     */
    void onAuthenticationFailed(HttpServletRequest req, HttpServletResponse res);

    /**
     * 自定義訪問資源權(quán)限不足時(shí)的處理方式,例如返回json格式的錯誤信息
     * {\"code\":403,\"message\":\"permission denied眨补!\")
     */
    void onAuthorizationFailed(HttpServletRequest req, HttpServletResponse res);

}

其中UserInfo類封裝了認(rèn)證用戶所擁有的權(quán)限信息

public class UserInfo {

  /**
    * 用戶的唯一標(biāo)識
    */
   private String account;

   /**
     * accessToken的密鑰管削,用于對accessToken進(jìn)行加密和解密
     * 建議為每個(gè)用戶配置不同的密鑰(比如使用用戶的password)
     */
   private String secret;

   /**
    * 用戶權(quán)限集合,含義類似于Shiro中的perms
    */
   private Set<String> permissions;

}

通過將認(rèn)證和授權(quán)邏輯與accessToken的處理進(jìn)行分離撑螺,應(yīng)用程序就可以僅僅關(guān)注于具體的權(quán)限管理模型的實(shí)現(xiàn)含思,而無需操心accessToken的相關(guān)問題了。這里有一個(gè)地方與常見的權(quán)限模型有一些差異甘晤。通常的系統(tǒng)一般采用基于角色的訪問控制模型(RBAC)含潘,主要由三個(gè)主體構(gòu)成:用戶(User) — 角色(Role)— 權(quán)限(Permission)。但我在這里省略掉了Role這樣一個(gè)主體安皱,用戶的授權(quán)信息中直接包含了該用戶的權(quán)限(Permission)调鬓,并沒有Role的相關(guān)信息。這樣設(shè)計(jì)最大的好處就是簡單酌伊,一個(gè)鏈接所對應(yīng)的權(quán)限僅僅只有Permission。而不像Shiro原本那樣缀踪,一個(gè)鏈接的訪問權(quán)限既可以使用Role又可以使用Permission來控制居砖,如果使用不當(dāng)反而會出現(xiàn)安全漏洞。但如果系統(tǒng)中要求使用角色來控制權(quán)限怎么辦呢驴娃?其實(shí)在UserInfo中省略掉Role并不意味著不能有Role的存在奏候,應(yīng)用程序在實(shí)現(xiàn)權(quán)限模型的時(shí)候可以完全按照自身的需求,只是在最終返回UserInfo的時(shí)候需要將Role轉(zhuǎn)換成Permission唇敞。比如通過account去查詢用戶的角色蔗草,再將返回該用戶所有角色所具備的權(quán)限就行了。這樣的實(shí)現(xiàn)其實(shí)比Role更加的靈活疆柔,比如某些系統(tǒng)的用戶的權(quán)限不是由角色決定的咒精,而是用戶所在的部門決定的,那只需要在實(shí)現(xiàn)getUserInfo方法的時(shí)候旷档,返回用戶所在部門的權(quán)限就好了模叙。

權(quán)限過濾器

說完最核心的認(rèn)證和授權(quán)過程,我們再來看一看Shiro框架的Filter機(jī)制鞋屈。Realm中的認(rèn)證和授權(quán)過程最終就是在各個(gè)Filter中觸發(fā)的范咨。這里的Filter并不是Java Servlet規(guī)范中定義的Filter,而是Shrio內(nèi)置的用于控制資源訪問的不同規(guī)則厂庇。Shiro內(nèi)置了很多Filter的實(shí)現(xiàn)渠啊,但最常用的有4種:

  1. anon: 匿名訪問過濾器,添加了此過濾器的資源無需任何驗(yàn)證即可訪問权旷。例如:/login/**=anon
  2. authc:認(rèn)證過濾器替蛉,通過調(diào)用subject.isAuthenticated來判斷當(dāng)前用戶是否被認(rèn)證過,資源需要通過認(rèn)證(登錄)才能使用。例如:/api/**=authc
  3. roles: 角色過濾器灭返,通過調(diào)用subject.hasRole來判斷當(dāng)前用戶是否擁有指定的角色盗迟。例如:/admins/**=roles["admin"]
  4. perms: 權(quán)限過濾器, 通過調(diào)用subject.isPermitted來判斷當(dāng)前用戶是否擁有指定的權(quán)限,例如:/api/data/add = perms['data:modify']

roles過濾器因?yàn)榍笆龅脑蛭覀冞@里不會涉及熙含。anon罚缕,perms過濾器基本可以沿用Shiro的默認(rèn)實(shí)現(xiàn),但authc過濾器默認(rèn)是從用戶會員會話中去獲取用戶的認(rèn)證狀態(tài)的怎静,所以我們需要對其進(jìn)行一定的改造邮弹。以下是傳統(tǒng)認(rèn)證方式和無狀態(tài)認(rèn)證方式的流程對比:


Shiro authc認(rèn)證時(shí)序圖 (1).png
無狀態(tài)authc認(rèn)證時(shí)序圖 (1).png

重新實(shí)現(xiàn)authc過濾器的邏輯,需要繼承org.apache.shiro.web.filter.AccessControlFilter蚓聘,這個(gè)是Shiro用于資源訪問控制最基礎(chǔ)的filter腌乡。核心是要重寫isAccessAllowed和onAccessDenied方法,大致代碼如下:


   @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //從header或URL參數(shù)中查找token
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(headerKeyOfToken);
        if (Strings.isNullOrEmpty(authorization)) {
            authorization = req.getParameter(headerKeyOfToken);
        }
        JWTToken token = new JWTToken(authorization);
        try {
            getSubject(request, response).login(token);
        } catch (Exception e) {
            logger.error("認(rèn)證失敗:" + e.getMessage());
            return false;
        }
        return true;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        this.userAuthService.onAuthenticationFailed((HttpServletRequest) request, (HttpServletResponse) response);
        return false;
    }

資源和權(quán)限的映射

Shiro 最常用的兩種將資源和權(quán)限進(jìn)行關(guān)聯(lián)的方式:

  1. 通過外部配置夜牡,將url和權(quán)限綁定与纽。例如:
<property name="filterChainDefinitions"> 
    <value>
      /static/** = anon
      /api/** = authc
      /api/user = perms["user"]
    </value>
</property>
  1. 基于annotation將類或方法的執(zhí)行與用戶權(quán)限進(jìn)行綁定,例如:
//執(zhí)行這個(gè)方法塘装,需要當(dāng)前用戶具有user:modify權(quán)限
@RequiresPermissions("user:modify")
public void modifyUserInfo(){
}

這兩種方式都有一定的缺點(diǎn)急迂。第一種將配置放到獨(dú)立的配置文件中,與代碼分離了蹦肴。而系統(tǒng)暴露的服務(wù)地址隨時(shí)都有可能發(fā)生變更僚碎,一旦代碼與配置沒有及時(shí)同步更新,就會出現(xiàn)安全隱患阴幌;第二種基于Annotation的配置能很好的解決這個(gè)問題勺阐,但是Shiro原生的注解是基于AOP的, 必須要求被保護(hù)的類啟用動態(tài)代理。而且每個(gè)需要被保護(hù)的類或者方法都需要添加對應(yīng)的注解矛双,無法像配置url那樣使用模式匹配渊抽。

我在實(shí)現(xiàn)上將兩種方式綜合了一下,使用基于URL的注解方式來盡可能避免上述的缺陷背零。我定義了兩個(gè)新的注解AlowAnonymous和RequiresPerms腰吟,和Shiro原生注解的區(qū)別是這兩個(gè)注解必須要與Spirng的RequestMapping(包括GetMapping,PostMapping等)注解結(jié)合進(jìn)行使用。無需動態(tài)代理徙瓶,框架會通過獲取RequestMapping定義的url毛雇,將其自動與RequiresPerms標(biāo)注的權(quán)限字段進(jìn)行綁定,這也意味著這兩個(gè)注解只允許在Controller中進(jìn)行使用侦镇。

@RestController
@RequestMapping("/api/user")
@RequiresPerms("user:basic")
public class UserController {

    @AlowAnonymous
    @PostMapping("/login")
    public String login() {
       return "ok";
    }

    @GetMapping("/detail")
    public String getUserDetail() {
       return "ok";
    }

    @PostMapping("/modify")
    @RequiresPerms("user:modify")
    public String modifyUser() {
        return "ok";
    }

    @PostMapping("/delete")
    @RequiresPerms({"system","user:delete"})
    public String deleteUser() {
        return "ok";
    }

    @PostMapping("/modify-logs")
    @RequiresPerms(value={"system","user:logs"}, logical = Logical.OR)
    public String deleteUser() {
        return "ok";
    }
}

例如上面的代碼等同于如下的Shiro配置:

/api/user/login     = anon
/api/user/detail    = perms[ user:basic ]
/api/user/modify  = perms[ user:modify ]
/api/user/delete   = perms[ system,user:delete ]
#默認(rèn)的shiro配置并不支持配置OR的比較操作符灵疮,這里的anyPerms是自定義過濾器
/api/user/modify-logs= anyPerms[ system,user:logs ]

為了進(jìn)一步減少一些無謂配置,框架默認(rèn)所有被攔截的資源必須是要經(jīng)過認(rèn)證的用戶才可以被訪問壳繁。即如果配置的攔截范圍是/api/,則會添加一條默認(rèn)的驗(yàn)證規(guī)則: /api/=authc震捣。但任何通過注解添加的驗(yàn)證規(guī)則都擁有比默認(rèn)規(guī)則更高的優(yōu)先級荔棉。

accessToken的自動刷新

accessToken是客戶端用于訪問授權(quán)資源的重要憑證,accessToken本身是由客戶端進(jìn)行維護(hù)的蒿赢,存在泄漏或者被截取的危險(xiǎn)润樱。為了最大程度的保證安全,accessToken本身必須包含一個(gè)合理的有效期限羡棵。過期之后壹若,必須重新進(jìn)行客戶端的認(rèn)證過程,獲取新的token皂冰。但這里存在一個(gè)問題店展,客戶端可能無法獲取到Token的實(shí)際超時(shí)時(shí)間(或者由于時(shí)鐘同步的原因不能精確的判定),如果等到服務(wù)端返回token失效的信息后再重新請求認(rèn)證秃流,必然會導(dǎo)致當(dāng)前處理流程的中斷赂蕴,如果是面向用戶的web系統(tǒng),則意味著用戶的操作被強(qiáng)制中斷需要重新進(jìn)行登錄舶胀。這樣的用戶體驗(yàn)顯然是不好的概说。考慮到傳統(tǒng)的基于Session的web應(yīng)用嚣伐,用戶的每次后臺操作都會刷新Session的過期時(shí)間席怪,只要用戶持續(xù)的操作Session就不會過期,我在本框架中也引入了類似的Token刷新機(jī)制纤控,大概流程圖如下:


accessToken刷新流程圖.png

如果開啟token的自動刷新,框架會自動注冊一個(gè)Spring HandlerInterceptor來攔截所有被保護(hù)的接口碉纺。在檢測到token即將過期船万,但還沒有超過最大生命周期時(shí),就會自動刷新token并在響應(yīng)的header中加入該token骨田。這樣客戶端可以通過每次檢查請求的響應(yīng)頭耿导,如果發(fā)現(xiàn)攜帶了新的token,就自動更新自身存儲的token态贤。這樣只要在token的生命周期內(nèi)不斷有新的請求舱呻,則token就會不斷的刷新。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悠汽,一起剝皮案震驚了整個(gè)濱河市箱吕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柿冲,老刑警劉巖茬高,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異假抄,居然都是意外死亡怎栽,警方通過查閱死者的電腦和手機(jī)丽猬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熏瞄,“玉大人脚祟,你說我怎么就攤上這事∏恳” “怎么了由桌?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胡陪。 經(jīng)常有香客問我沥寥,道長,這世上最難降的妖魔是什么柠座? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任邑雅,我火速辦了婚禮,結(jié)果婚禮上妈经,老公的妹妹穿的比我還像新娘淮野。我一直安慰自己,他們只是感情好吹泡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布骤星。 她就那樣靜靜地躺著,像睡著了一般爆哑。 火紅的嫁衣襯著肌膚如雪洞难。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天揭朝,我揣著相機(jī)與錄音队贱,去河邊找鬼。 笑死潭袱,一個(gè)胖子當(dāng)著我的面吹牛柱嫌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屯换,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼编丘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了彤悔?” 一聲冷哼從身側(cè)響起嘉抓,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜗巧,沒想到半個(gè)月后掌眠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幕屹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年蓝丙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了级遭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渺尘,死狀恐怖挫鸽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸥跟,我是刑警寧澤丢郊,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站医咨,受9級特大地震影響枫匾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拟淮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一干茉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧很泊,春花似錦角虫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至昏兆,卻和暖如春枫虏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爬虱。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工模软, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饮潦。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像携狭,于是被迫代替她去往敵國和親继蜡。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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