Spring Security 解析(二) —— 認(rèn)證過程

Spring Security 解析(二) —— 認(rèn)證過程

??在學(xué)習(xí)Spring Cloud 時(shí),遇到了授權(quán)服務(wù)oauth 相關(guān)內(nèi)容時(shí)伦忠,總是一知半解,因此決定先把Spring Security 、Spring Security Oauth2 等權(quán)限、認(rèn)證相關(guān)的內(nèi)容若河、原理及設(shè)計(jì)學(xué)習(xí)并整理一遍。本系列文章就是在學(xué)習(xí)的過程中加強(qiáng)印象和理解所撰寫的寞宫,如有侵權(quán)請告知萧福。

項(xiàng)目環(huán)境:

  • JDK1.8
  • Spring boot 2.x
  • Spring Security 5.x

一、@EnableGlobalAuthentication 配置 解析

??還記得上一篇講解授權(quán)過程中提到@EnableWebSecurity 引用了 WebSecurityConfiguration 配置類 和 @EnableGlobalAuthentication 注解嗎辈赋? 當(dāng)時(shí)只是講解了下 WebSecurityConfiguration 配置類 鲫忍,這次該輪到 @EnableGlobalAuthentication 配置了。

??查看 @EnableGlobalAuthentication 注解源碼钥屈,我們可以看到其引用了AuthenticationConfiguration 配置類悟民。其中有一個(gè)方法值得我們注意,那就是 getAuthenticationManager() (還記得授權(quán)過程中調(diào)用了 AuthenticationManager().authenticate() 進(jìn)行認(rèn)證么焕蹄?), 我們來看下其源碼內(nèi)部大致邏輯:

public AuthenticationManager getAuthenticationManager() throws Exception {

        ......
        // 1 調(diào)用 authenticationManagerBuilder 方法獲取 authenticationManagerBuilder 對象逾雄,用于 build  authenticationManager 對象
        AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
                this.objectPostProcessor, this.applicationContext);
        .....
        // 2  build 方法調(diào)用同授權(quán)過程中的 webSecurity.build()  一樣阀溶,都是通過父類 AbstractConfiguredSecurityBuilder.doBuild() 方法中的 performBuild() 方法進(jìn)行 build, 只是這里不再是通過其子類 HttpSecurity.performBuild() 腻脏,而是通過 AuthenticationManagerBuilder.performBuild() 
        authenticationManager = authBuilder.build();

        .......
        
        return authenticationManager;
    }

根據(jù)源碼我們可以概括其邏輯分2部分:

  • 1、 通過調(diào)用 authenticationManagerBuilder() 方法獲取 authenticationManagerBuilder 對象
  • 2银锻、 調(diào)用authenticationManagerBuilder 對象的 build() 創(chuàng)建 authenticationManager 對象并返回

??我們再詳細(xì)看下這個(gè)build的過程永品,可以發(fā)現(xiàn)其 build 調(diào)用跟授權(quán)過程中build securityFilterChain 一樣 都是通過 AbstractConfiguredSecurityBuilder.doBuild() 方法中的 performBuild() 進(jìn)行構(gòu)建, 不過這次不再是調(diào)用其子類 HttpSecurity.performBuild() 而是 AuthenticationManagerBuilder.performBuild() 击纬。
我們來看下 AuthenticationManagerBuilder.performBuild() 方法內(nèi)部實(shí)現(xiàn):

protected ProviderManager performBuild() throws Exception {
        if (!isConfigured()) {
            logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
            return null;
        }
        // 1  創(chuàng)建了一個(gè)包含  authenticationProviders  參數(shù) 的 ProviderManager 對象
        ProviderManager providerManager = new ProviderManager(authenticationProviders,
                parentAuthenticationManager);
        if (eraseCredentials != null) {
            providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
        }
        if (eventPublisher != null) {
            providerManager.setAuthenticationEventPublisher(eventPublisher);
        }
        providerManager = postProcess(providerManager);
        return providerManager;
    }

?? 這里我們主要關(guān)注其內(nèi)部 創(chuàng)建了一個(gè)包含 authenticationProviders 參數(shù) 的 ProviderManager (ProviderManager 是 AuthenticationManager 的實(shí)現(xiàn)類)對象并返回鼎姐。

回過頭,我們來看下 AuthenticationManager 接口 源碼:

public interface AuthenticationManager {
    // 認(rèn)證接口
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

?? 可以看到更振,內(nèi)部就只有一個(gè)我們在授權(quán)過程中提到過的 authenticate()炕桨,其接口接收一個(gè) Authentication(這個(gè)對象我們也不陌生,之前授權(quán)過程中提到過的 UsernamePasswordAuthrnticationToken 等都是其實(shí)現(xiàn)子類) 對象作為參數(shù)肯腕。

??至此認(rèn)證的部分關(guān)鍵類或接口已經(jīng)浮出水面了献宫,它們分別是 AuthenticationManager 、ProviderManager实撒、AuthenticationProvider姊途、Authentication, 接下來我們就圍繞這幾個(gè)類或接口進(jìn)行剖析知态。

二捷兰、AuthenticationManager

??正如我們之前看到的一項(xiàng),它是整個(gè)認(rèn)證的入口负敏,其定義的認(rèn)證接口 authenticate() 接收一個(gè) Authentication 對象作為參數(shù)贡茅。AuthenticationManager 它只是提供了一個(gè)認(rèn)證接口方法,因?yàn)樵趯?shí)際使用中,我們不僅有賬戶密碼的登錄方式顶考,還有短信驗(yàn)證碼登錄彤叉、郵箱登錄等等,所以它本身不做任何認(rèn)證村怪,其具體做認(rèn)證的是 ProviderManager 子類秽浇,但正如我們說過的認(rèn)證方式有很多,如果僅僅依靠 ProviderManager 本身來實(shí)現(xiàn) authenticate() 接口甚负,那我們要支持這么多認(rèn)證方式不得寫多少個(gè) if 判斷柬焕,而且以后如果我們想要支持指紋登錄,那又不得不在這個(gè)方法內(nèi)部加個(gè)if梭域,這種不利于系統(tǒng)擴(kuò)展的寫法肯定是不可取的斑举,所以 ProviderManager 本身會維護(hù)一個(gè)List<AuthenticationProvider>列表 ,用于存放多種認(rèn)證方式病涨,然后通過委托的方式富玷,調(diào)用 AuthenticationProvider 來真正實(shí)現(xiàn)認(rèn)證邏輯的 。 而 Authentication 就是我們需要認(rèn)證的信息(當(dāng)然不僅僅只包括賬戶信息)既穆,通過authenticate() 接口認(rèn)證成功后返回的 Authentication 就是一個(gè)被標(biāo)識認(rèn)證成功的對象 赎懦。 這里為什么要解釋下 AuthenticationManager、ProviderManager幻工、AuthenticationProvider 的關(guān)系励两,主要是一開始容易搞混它們,相信經(jīng)過這樣一段描述更容易理解了吧囊颅。当悔。。

三踢代、Authentication

?? 如果 沒有看過源碼的同學(xué)可能會認(rèn)為 Authentication 是一個(gè)類吧盲憎,可實(shí)際上它是一個(gè) 接口,其內(nèi)部并未存在任何屬性字段胳挎,它僅僅定義了和規(guī)范好了認(rèn)證對象需要的接口方法饼疙,我們來看看其定義的接口方法有哪些,分別又什么作用:

public interface Authentication extends Principal, Serializable { 

    // 1  獲取權(quán)限信息(不能僅僅理解未角色權(quán)限串远,還有菜單權(quán)限等等)宏多,默認(rèn)是GrantedAuthority接口的實(shí)現(xiàn)類
    Collection<? extends GrantedAuthority> getAuthorities();

    // 2 獲取用戶密碼信息 ,認(rèn)證成功后會被刪除掉
    Object getCredentials();
    
    // 3  主要存放訪問著的ip等信息
    Object getDetails();

    // 4  重點(diǎn)T璺!伸但! 最重要的身份信息。 大部分情況下是 UserDetails 接口的實(shí)現(xiàn) 類留搔,比如 我們 之前配置的 User 對象   
    Object getPrincipal();

    // 5  是否認(rèn)證(成功)
    boolean isAuthenticated();

    // 6  設(shè)置認(rèn)證標(biāo)識 
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

?? 既然 Authentication 定義了這些接口方法更胖,那么其子類實(shí)現(xiàn)肯定都按照這個(gè)標(biāo)準(zhǔn)或者稱之為規(guī)范定制了實(shí)現(xiàn),這里就不羅列出其子類的具體實(shí)現(xiàn)了,有興趣的同學(xué)可以去看下 我們最常用的 UsernamePasswordAuthenticationToken 實(shí)現(xiàn)(包括其 父類 AbstractAuthenticationToken)

四却妨、ProviderManager

?? 它是 AuthenticationManager 的實(shí)現(xiàn)子類之一饵逐,也是我們最常用的一個(gè)實(shí)現(xiàn)。正如我們前面提到過的彪标,其內(nèi)部維護(hù)了 一個(gè) List<AuthenticationProvider> 對象倍权, 用于支持和擴(kuò)展 多種形式的認(rèn)證方式。我們來看下 其 實(shí)現(xiàn) authenticate() 的源碼:

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
            
        ......
        
        // 1 通過 getProviders() 方法獲取到內(nèi)部維護(hù)的 List<AuthenticationProvider> 對象 并 通過遍歷的方式 去 認(rèn)證捞烟,只要認(rèn)證成功 就 break 
        for (AuthenticationProvider provider : getProviders()) {
            //  2 正如前面看到的有 很多 AuthenticationProvider 實(shí)現(xiàn)薄声,如果每次都是驗(yàn)證失敗后再掉用下一個(gè) AuthenticationProvider 這種實(shí)現(xiàn)是不是很不高效? 所以 這里通過  supports() 方法來驗(yàn)證是否可以使用 該 AuthenticationProvider 進(jìn)行驗(yàn)證题画,不可以就直接換下一個(gè) 
            if (!provider.supports(toTest)) {
                continue;
            }
            try {
                // 3  重點(diǎn)默辨,這里是 調(diào)用真實(shí)的認(rèn)證方法
                result = provider.authenticate(authentication);
                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException e) {
                prepareException(e, authentication);
                throw e;
            }
            catch (InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                throw e;
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }
        
        if (result == null && parent != null) {
            try {
                // 4 前面都認(rèn)證不成功,調(diào)用父類(嚴(yán)格意思不是調(diào)用父類苍息,而是其他的 AuthenticationManager 實(shí)現(xiàn)類)認(rèn)證方法
                result = parentResult = parent.authenticate(authentication);
            }
            catch (ProviderNotFoundException e) {
        
            }
            catch (AuthenticationException e) {
                lastException = parentException = e;
            }
        }

        if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                //  5  刪除認(rèn)證成功后的 密碼信息缩幸,保證安全
                ((CredentialsContainer) result).eraseCredentials();
            }
            if (parentResult == null) {
                eventPublisher.publishAuthenticationSuccess(result);
            }
            return result;
        }
        
        if (lastException == null) {
            lastException = new ProviderNotFoundException(messages.getMessage(
                    "ProviderManager.providerNotFound",
                    new Object[] { toTest.getName() },
                    "No AuthenticationProvider found for {0}"));
        }
        if (parentException == null) {
            prepareException(lastException, authentication);
        }

        throw lastException;
    }

?? 梳理下整個(gè)方法內(nèi)部實(shí)現(xiàn)邏輯:

  • 通過 getProviders() 方法獲取到內(nèi)部維護(hù)的 List<AuthenticationProvider> 對象 并 通過遍歷的方式 去 認(rèn)證
  • 通過 provider.supports() 方法 來驗(yàn)證是否可用當(dāng)前的 AuthenticationProvider 進(jìn)行驗(yàn)證,不可以就直接換下一個(gè) ( 其實(shí)方法內(nèi)部就是驗(yàn)證當(dāng)前 的 Authentication 對象是不是其某個(gè)子類竞思,比如 我們最常用到的 DaoAuthenticationProvider 的 supports 方法就是判斷當(dāng)前 的 Authentication 是不是 UsernamePasswordAuthenticationToken )
  • 通過 provider.authenticate() 調(diào)用 其真正的認(rèn)證實(shí)現(xiàn)
  • 如果 前面的所有 AuthenticationProvider 均不能認(rèn)證成功表谊,嘗試調(diào)用 parent.authenticate() 方法 :調(diào)用父類(嚴(yán)格意思不是調(diào)用父類,而是其他的 AuthenticationManager 實(shí)現(xiàn)類)認(rèn)證方法
  • 最后 通過 ((CredentialsContainer) result).eraseCredentials() 刪除認(rèn)證成功后的 密碼信息衙四,保證安全

五铃肯、AuthenticationProvider(DaoAuthenticationProvider)

?? 正如我們想象的一樣,AuthenticationProvider 是一個(gè)接口传蹈,本身定義了一個(gè) 和 AuthenticationManager 一樣的 authenticate 認(rèn)證接口方法,外加一個(gè) supports() 用于 判別當(dāng)前 Authentication 是否可以進(jìn)行處理步藕。

public interface AuthenticationProvider {
    // 定義認(rèn)證接口方法
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
    // 定義判斷是否可以認(rèn)證處理的接口方法
    boolean supports(Class<?> authentication);
}

?? 這里我們就拿我們用得最多的一個(gè) AuthenticationProvider 實(shí)現(xiàn)類 DaoAuthenticationProvider(注意惦界,這里和UsernamePasswordAuthenticationFilter 類似,都是通過父類來實(shí)現(xiàn)接口咙冗,然后內(nèi)部處理方法再調(diào)用 其 子類進(jìn)行處理) 來看其內(nèi)部 這2個(gè)抽象方法的實(shí)現(xiàn):

  • supports 實(shí)現(xiàn):
public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class
                .isAssignableFrom(authentication));
    }

?? 可以看到僅僅只是判斷當(dāng)前的 authentication 是否為 UsernamePasswordAuthenticationToken(或其子類)

  • authrnticate 實(shí)現(xiàn)
// 1 注意這里的實(shí)現(xiàn)方法是 DaoAuthenticationProvider 的父類 AbstractUserDetailsAuthenticationProvider 實(shí)現(xiàn)的
public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
    
        // 2 從 authentication 中獲取 用戶名
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();

        boolean cacheWasUsed = true;
        
        // 3 根據(jù)username 從緩存中獲取 認(rèn)證成功的 UserDetails 信息
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                // 4 如果緩存中沒有用戶信息 需要 獲取用戶信息(由 DaoAuthenticationProvider 實(shí)現(xiàn) ) 
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                ......
            }
        }

        try {
            // 5 前置檢查賬戶是否鎖定沾歪,過期,凍結(jié)(由DefaultPreAuthenticationChecks類實(shí)現(xiàn))
            preAuthenticationChecks.check(user);
            // 6 主要是驗(yàn)證 獲取到的用戶密碼與傳入的用戶密碼是否一致
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
            // 這里官方發(fā)現(xiàn)緩存可能導(dǎo)致了某些問題雾消,又重新去認(rèn)證一次
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }
        // 7 后置檢查用戶密碼是否 過期
        postAuthenticationChecks.check(user);
        
        // 8 驗(yàn)證成功后的用戶信息存入緩存
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        // 9 重新創(chuàng)建一個(gè) authenticated 為true (即認(rèn)證成功)的 UsernamePasswordAuthenticationToken 對象并返回 
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

?? 梳理下authenticate(這里的方法的實(shí)現(xiàn)是由 AbstractUserDetailsAuthenticationProvider 提供的)方法內(nèi)部實(shí)現(xiàn)邏輯:

  • 從 入?yún)?authentication 對象中獲取到 username 信息
  • (這里忽略緩存的處理) 調(diào)用 retrieveUser() 方法(由 DaoAuthenticationProvider 實(shí)現(xiàn))根據(jù) username 獲取到 系統(tǒng)(一般來說是從數(shù)據(jù)庫中) 中獲取到 UserDetails 對象
  • 通過 preAuthenticationChecks.check() 方法檢測 當(dāng)前獲取到的 UserDetails 是否過期灾搏、凍結(jié)、鎖定(如果任意一個(gè)條件 為 true 將拋出 相應(yīng) 的異常)
  • 通過 additionalAuthenticationChecks() (由 DaoAuthenticationProvider 實(shí)現(xiàn)) 判斷 密碼是否一致
  • 通過 postAuthenticationChecks.check() 檢測 UserDetails 的密碼是否過期
  • 最后通過 createSuccessAuthentication() 重新創(chuàng)建一個(gè) authenticated 為true (即認(rèn)證成功)的 UsernamePasswordAuthenticationToken 對象并返回

??雖然我們知道其驗(yàn)證邏輯立润, 但其內(nèi)部很多方法我們不清楚其內(nèi)部實(shí)現(xiàn),以及這里新增的一個(gè) 關(guān)鍵認(rèn)證類 UserDetails 是怎么設(shè)計(jì)的,如何驗(yàn)證其是否過期等等咳焚。

六津函、 UserDetailsService 和 UserDetails

??繼續(xù)深入看下 retrieveUser() 方法,首先我們注意到其返回對象是一個(gè) UserDetails,那么我們先從 UserDetails 入手。

UserDetails:

?? 我們先來看下 UserDetails 源碼:

public interface UserDetails extends Serializable {
    
    // 1 與 Authentication 的 一樣丛晦,都是獲取 權(quán)限信息 
    Collection<? extends GrantedAuthority> getAuthorities();

    // 2 獲取用戶正確的密碼   
    String getPassword();

    // 3 獲取賬戶名
    String getUsername();

    // 4 賬戶是否過期
    boolean isAccountNonExpired();

    // 5 賬戶是否鎖定
    boolean isAccountNonLocked();

    // 6 密碼是否過期 
    boolean isCredentialsNonExpired();

    // 7 賬戶是否凍結(jié)
    boolean isEnabled();
}

?? 從上面的 4奕纫,5,6烫沙,7 接口我們就能夠知道 preAuthenticationChecks.check() 和 postAuthenticationChecks.check() 是如何檢測的了匹层,這里2個(gè)方法的檢測細(xì)節(jié)就不再深究了,有興趣的同學(xué)可以看看源碼锌蓄,我們只要知道檢測失敗會拋出異常就行了又固。

??咋呼一看,這個(gè)UserDetails 和 Authentication 很相似煤率,其實(shí)它們之間還真有關(guān)系仰冠,在createSuccessAuthentication() 傳教Authentication 對象時(shí),它的authorities 就是UserDetails 傳入的蝶糯。

UserDetailsService:

??retrieveUser() 方法是系統(tǒng)通過傳入的賬戶名獲取對應(yīng)的賬戶信息的唯一方法洋只,我們來看下其內(nèi)部源碼邏輯:

protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
        
            // 通過 UserDetailsService 的loadUserByUsername 方法 獲取用戶信息
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            ......
        }
    }

?? 相信看到這里,一切都關(guān)聯(lián)上了昼捍,這里的 UserDetailsService.loadUserByUsername() 就是我們在 上一篇 授權(quán)過程中 我們自己實(shí)現(xiàn)的识虚。 這里就不再 貼出UserDetailsService 源碼了。

?? 還有additionalAuthenticationChecks() 密碼驗(yàn)證沒有講到妒茬,這里簡單提下担锤,其內(nèi)部就是通過 PasswordEncoder.matches() 方法進(jìn)行密碼匹配的。不過這里要注意一下乍钻,這里的 PasswordEncoder 在 Security 5 開始默認(rèn) 替換成了 DelegatingPasswordEncoder 這里也是和我們之前 討論 loadUserByUsername 方法內(nèi)部創(chuàng)建User (UserDeatails 實(shí)現(xiàn)類之一)是一定要用到 PasswordEncoderFactories.createDelegatingPasswordEncoder().encode() 加密是相應(yīng)的肛循。

七、個(gè)人總結(jié)

?? 認(rèn)證的頂級管理員 AuthenticationManager 為我們提供了 認(rèn)證入口( authenticate()接口)银择,但是呢多糠,我們也知道大老板一般不直接參與實(shí)質(zhì)的工作,所以它把任務(wù)安排給它的下屬浩考,也就是我們的 ProviderManager 部門領(lǐng)導(dǎo) 夹孔,部門領(lǐng)導(dǎo) 肩負(fù)起 認(rèn)證的工作(authenticate() 認(rèn)證的實(shí)現(xiàn)),其實(shí)呢析孽,我們也知道部門領(lǐng)導(dǎo)也是 直接參數(shù) 認(rèn)證工作的搭伤,它都是將實(shí)際任務(wù)安排給小組長的, 也就是我們的 AuthrnticationProvider 袜瞬,部門領(lǐng)導(dǎo) 開個(gè)會議怜俐,聚集了所有小組長 ,讓它們自行判斷(通過
support()) 大老板交下來的任務(wù) 該由誰來完成吞滞, 小組長 領(lǐng)到任務(wù)后佑菩,就把任務(wù) 分發(fā)給各個(gè)小組成員盾沫,比如 成員1(UserDetailsService) 只需要 完成 retrieveUser() 的工作,然后成員2 完成 additionalAuthenticationChecks() 的工作殿漠,最后由項(xiàng)目經(jīng)理 ( createSuccessAuthentication() ) 將結(jié)果匯報(bào)給小組長赴精,然后小組長匯報(bào)給部門領(lǐng)導(dǎo),部門領(lǐng)導(dǎo) 審核一下結(jié)果绞幌,覺得小組長做得不夠好蕾哟,然后又做了一些操作 ( eraseCredentials() 擦除密碼信息 ),最后認(rèn)為 結(jié)果 可以了就匯報(bào)給老板莲蜘,老板呢谭确,也不多看,直接將結(jié)果給了客戶(filter)票渠。

?? 按照慣例逐哈,上流程圖:

?? 本文介紹認(rèn)證過程的代碼可以訪問代碼倉庫中的 security 模塊 ,項(xiàng)目的github 地址 : https://github.com/BUG9/spring-security

?? ?? ?? 如果您對這些感興趣问顷,歡迎star昂秃、follow、收藏杜窄、轉(zhuǎn)發(fā)給予支持肠骆!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市塞耕,隨后出現(xiàn)的幾起案子蚀腿,更是在濱河造成了極大的恐慌,老刑警劉巖扫外,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉钙,死亡現(xiàn)場離奇詭異,居然都是意外死亡畏浆,警方通過查閱死者的電腦和手機(jī)胆胰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刻获,“玉大人,你說我怎么就攤上這事瞎嬉⌒保” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵氧枣,是天一觀的道長沐兵。 經(jīng)常有香客問我,道長便监,這世上最難降的妖魔是什么扎谎? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任碳想,我火速辦了婚禮,結(jié)果婚禮上毁靶,老公的妹妹穿的比我還像新娘胧奔。我一直安慰自己,他們只是感情好预吆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布龙填。 她就那樣靜靜地躺著,像睡著了一般拐叉。 火紅的嫁衣襯著肌膚如雪岩遗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天凤瘦,我揣著相機(jī)與錄音宿礁,去河邊找鬼。 笑死蔬芥,一個(gè)胖子當(dāng)著我的面吹牛梆靖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坝茎,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼涤姊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗤放?” 一聲冷哼從身側(cè)響起思喊,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎次酌,沒想到半個(gè)月后恨课,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岳服,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年剂公,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吊宋。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纲辽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出璃搜,到底是詐尸還是另有隱情拖吼,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布这吻,位于F島的核電站吊档,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唾糯。R本人自食惡果不足惜怠硼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一鬼贱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧香璃,春花似錦这难、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至同云,卻和暖如春糖权,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炸站。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工星澳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旱易。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓禁偎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阀坏。 傳聞我的和親對象是個(gè)殘疾皇子如暖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

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