Spring Boot + Spring Security + Thymeleaf 簡(jiǎn)單教程

Spring Boot + Spring Security + Thymeleaf 簡(jiǎn)單教程

因?yàn)橛幸粋€(gè)項(xiàng)目需采用MVC構(gòu)架,所以學(xué)習(xí)了Spring Security并記錄下來(lái),希望大家一起學(xué)習(xí)提供意見

GitHub地址:https://github.com/Smith-Cruise/Spring-Boot-Security-Thymeleaf-Demo

原文地址:https://www.inlighting.org/archives/spring-boot-security-thymeleaf

如果有疑問(wèn)拌牲,請(qǐng)?jiān)?GitHub 中發(fā)布 issue颊亮,我有空會(huì)為大家解答的

本項(xiàng)目基于Spring Boot 2 + Spring Security 5 + Thymeleaf 2 + JDK11(你也可以用8阀捅,應(yīng)該區(qū)別不大)

實(shí)現(xiàn)了以下功能:

  • 基于注解的權(quán)限控制
  • 在Thymeleaf中使用Spring Security的標(biāo)簽
  • 自定義權(quán)限注解
  • 記住密碼功能

如果需要前后端分離的安全框架搭建教程可以參考:Spring Boot 2 + Spring Security 5 + JWT 的單頁(yè)應(yīng)用Restful解決方案

項(xiàng)目演示

如果想要直接體驗(yàn)宿饱,直接 clone 項(xiàng)目熏瞄,運(yùn)行 mvn spring-boot:run 命令即可進(jìn)行訪問(wèn),網(wǎng)址規(guī)則自行看教程后面

首頁(yè)

首頁(yè)

登入

登入

登出

登出

Home頁(yè)面

Home

Admin頁(yè)面

Admin

403無(wú)權(quán)限頁(yè)面

403

Spring Security 基本原理

Spring Security 過(guò)濾器鏈

Spring Security實(shí)現(xiàn)了一系列的過(guò)濾器鏈谬以,就按照下面順序一個(gè)一個(gè)執(zhí)行下去强饮。

  1. ....class 一些自定義過(guò)濾器(在配置的時(shí)候你可以自己選擇插到哪個(gè)過(guò)濾器之前),因?yàn)檫@個(gè)需求因人而異为黎,本文不探討邮丰,大家可以自己研究
  2. UsernamePasswordAithenticationFilter.class Spring Security 自帶的表單登入驗(yàn)證過(guò)濾器,也是本文主要使用的過(guò)濾器
  3. BasicAuthenticationFilter.class
  4. ExceptionTranslation.class 異常解釋器
  5. FilterSecurityInterceptor.class 攔截器最終決定請(qǐng)求能否通過(guò)
  6. Controller 我們最后自己編寫的控制器

相關(guān)類說(shuō)明

  • User.class :注意這個(gè)類不是我們自己寫的铭乾,而是Spring Security官方提供的剪廉,他提供了一些基礎(chǔ)的功能,我們可以通過(guò)繼承這個(gè)類來(lái)擴(kuò)充方法炕檩。詳見代碼中的 CustomUser.java
  • UserDetailsService.class: Spring Security官方提供的一個(gè)接口斗蒋,里面只有一個(gè)方法loadUserByUsername() ,Spring Security會(huì)調(diào)用這個(gè)方法來(lái)獲取數(shù)據(jù)庫(kù)中存在的數(shù)據(jù)捧书,然后和用戶POST過(guò)來(lái)的用戶名密碼進(jìn)行比對(duì)吹泡,從而判斷用戶的用戶名密碼是否正確。所以我們需要自己實(shí)現(xiàn)loadUserByUsername() 這個(gè)方法经瓷。詳見代碼中的 CustomUserDetailsService.java爆哑。

項(xiàng)目邏輯

為了體現(xiàn)權(quán)限區(qū)別,我們通過(guò)HashMap構(gòu)造了一個(gè)數(shù)據(jù)庫(kù)舆吮,里面包含了4個(gè)用戶

ID 用戶名 密碼 權(quán)限
1 jack jack123 user
2 danny danny123 editor
3 alice alice123 reviewer
4 smith smith123 admin

說(shuō)明下權(quán)限

user:最基礎(chǔ)的權(quán)限揭朝,只要是登入用戶就有 user 權(quán)限

editor:在 user 權(quán)限上面增加了 editor 的權(quán)限

reviewer:與上同理,editorreviewer 屬于同一級(jí)的權(quán)限

admin:包含所有權(quán)限

為了檢驗(yàn)權(quán)限色冀,我們提供若干個(gè)頁(yè)面

網(wǎng)址 說(shuō)明 可訪問(wèn)權(quán)限
/ 首頁(yè) 所有人均可訪問(wèn)(anonymous)
/login 登入頁(yè)面 所有人均可訪問(wèn)(anonymous)
/logout 退出頁(yè)面 所有人均可訪問(wèn)(anonymous)
/user/home 用戶中心 user
/user/editor editor, admin
/user/reviewer reviewer, admin
/user/admin admin
/403 403錯(cuò)誤頁(yè)面潭袱,美化過(guò),大家可以直接用 所有人均可訪問(wèn)(anonymous)
/404 404錯(cuò)誤頁(yè)面锋恬,美化過(guò)屯换,大家可以直接用 所有人均可訪問(wèn)(anonymous)
/500 500錯(cuò)誤頁(yè)面,美化過(guò)与学,大家可以直接用 所有人均可訪問(wèn)(anonymous)

代碼配置

Maven 配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.inlighting</groupId>
    <artifactId>security-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security-demo</name>
    <description>Demo project for Spring Boot &amp; Spring Security</description>

    <!--指定JDK版本彤悔,大家可以改成自己的-->
    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--對(duì)Thymeleaf添加Spring Security標(biāo)簽支持-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--開發(fā)的熱加載配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.properties配置

為了使熱加載(這樣修改模板后無(wú)需重啟 Tomcat )生效,我們需要在Spring Boot的配置文件上面加上一段話

spring.thymeleaf.cache=false

如果需要詳細(xì)了解熱加載索守,請(qǐng)看官方文檔:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-hotswapping

Spring Security 配置

首先我們開啟方法注解支持:只需要在類上添加 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 注解晕窑,我們?cè)O(shè)置 prePostEnabled = true 是為了支持 hasRole() 這類表達(dá)式。如果想進(jìn)一步了解方法注解可以看 Introduction to Spring Method Security 這篇文章卵佛。

SecurityConfig.java

/**
 * 開啟方法注解支持杨赤,我們?cè)O(shè)置prePostEnabled = true是為了后面能夠使用hasRole()這類表達(dá)式
 * 進(jìn)一步了解可看教程:https://www.baeldung.com/spring-security-method-security
 */
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * TokenBasedRememberMeServices的生成密鑰敞斋,
     * 算法實(shí)現(xiàn)詳見文檔:https://docs.spring.io/spring-security/site/docs/5.1.3.RELEASE/reference/htmlsingle/#remember-me-hash-token
     */
    private final String SECRET_KEY = "123456";

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    /**
     * 必須有此方法,Spring Security官方規(guī)定必須要有一個(gè)密碼加密方式疾牲。
     * 注意:例如這里用了BCryptPasswordEncoder()的加密方法植捎,那么在保存用戶密碼的時(shí)候也必須使用這種方法说敏,確保前后一致医咨。
     * 詳情參見項(xiàng)目中Database.java中保存用戶的邏輯
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置Spring Security谴忧,下面說(shuō)明幾點(diǎn)注意事項(xiàng)。
     * 1. Spring Security 默認(rèn)是開啟了CSRF的昏兆,此時(shí)我們提交的POST表單必須有隱藏的字段來(lái)傳遞CSRF,
     * 而且在logout中,我們必須通過(guò)POST到 /logout 的方法來(lái)退出用戶,詳見我們的login.html和logout.html.
     * 2. 開啟了rememberMe()功能后,我們必須提供rememberMeServices忘瓦,例如下面的getRememberMeServices()方法境蜕,
     * 而且我們只能在TokenBasedRememberMeServices中設(shè)置cookie名稱戴尸、過(guò)期時(shí)間等相關(guān)配置,如果在別的地方同時(shí)配置拉队,會(huì)報(bào)錯(cuò)秩彤。
     * 錯(cuò)誤示例:xxxx.and().rememberMe().rememberMeServices(getRememberMeServices()).rememberMeCookieName("cookie-name")
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login") // 自定義用戶登入頁(yè)面
                .failureUrl("/login?error") // 自定義登入失敗頁(yè)面鳍咱,前端可以通過(guò)url中是否有error來(lái)提供友好的用戶登入提示
                .and()
                .logout()
                .logoutUrl("/logout")// 自定義用戶登出頁(yè)面
                .logoutSuccessUrl("/")
                .and()
                .rememberMe() // 開啟記住密碼功能
                .rememberMeServices(getRememberMeServices()) // 必須提供
                .key(SECRET_KEY) // 此SECRET需要和生成TokenBasedRememberMeServices的密鑰相同
                .and()
                /*
                 * 默認(rèn)允許所有路徑所有人都可以訪問(wèn)澎现,確保靜態(tài)資源的正常訪問(wèn)渠欺。
                 * 后面再通過(guò)方法注解的方式來(lái)控制權(quán)限。
                 */
                .authorizeRequests().anyRequest().permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/403"); // 權(quán)限不足自動(dòng)跳轉(zhuǎn)403
    }

    /**
     * 如果要設(shè)置cookie過(guò)期時(shí)間或其他相關(guān)配置,請(qǐng)?jiān)谙路阶孕信渲?     */
    private TokenBasedRememberMeServices getRememberMeServices() {
        TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(SECRET_KEY, customUserDetailsService);
        services.setCookieName("remember-cookie");
        services.setTokenValiditySeconds(100); // 默認(rèn)14天
        return services;
    }
}

UserService.java

自己模擬數(shù)據(jù)庫(kù)操作的Service,用于向自己通過(guò)HashMap模擬的數(shù)據(jù)源獲取數(shù)據(jù)。

@Service
public class UserService {

    private Database database = new Database();

    public CustomUser getUserByUsername(String username) {
        CustomUser originUser = database.getDatabase().get(username);
        if (originUser == null) {
            return null;
        }

        /*
         * 此處有坑技俐,之所以這么做是因?yàn)镾pring Security獲得到User后暂刘,會(huì)把User中的password字段置空森缠,以確保安全。
         * 因?yàn)镴ava類是引用傳遞,為防止Spring Security修改了我們的源頭數(shù)據(jù)端盆,所以我們復(fù)制一個(gè)對(duì)象提供給Spring Security。
         * 如果通過(guò)真實(shí)數(shù)據(jù)庫(kù)的方式獲取费封,則沒有這種問(wèn)題需要擔(dān)心焕妙。
          */
        return new CustomUser(originUser.getId(), originUser.getUsername(), originUser.getPassword(), originUser.getAuthorities());
    }
}

CustomUserDetailsService.java

/**
 * 實(shí)現(xiàn)官方提供的UserDetailsService接口即可
 */
@Service
public class CustomUserDetailsService implements UserDetailsService {

    private Logger LOGGER = LoggerFactory.getLogger(getClass());

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        CustomUser user = userService.getUserByUsername(username);
        if (user == null) {
            throw new  UsernameNotFoundException("該用戶不存在");
        }
        LOGGER.info("用戶名:"+username+" 角色:"+user.getAuthorities().toString());
        return user;
    }
}

自定義權(quán)限注解

我們?cè)陂_發(fā)網(wǎng)站的過(guò)程中,比如 GET /user/editor 這個(gè)請(qǐng)求角色為 EDITORADMIN 肯定都可以弓摘,如果我們?cè)诿恳粋€(gè)需要判斷權(quán)限的方法上面寫一長(zhǎng)串的權(quán)限表達(dá)式焚鹊,一定很復(fù)雜。但是通過(guò)自定義權(quán)限注解韧献,我們可以通過(guò) @IsEditor 這樣的方法來(lái)判斷末患,這樣一來(lái)就簡(jiǎn)單了很多爷抓。進(jìn)一步了解可以看:Introduction to Spring Method Security

IsUser.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyAuthority('ROLE_USER', 'ROLE_EDITOR', 'ROLE_REVIEWER', 'ROLE_ADMIN')")
public @interface IsUser {
}

IsEditor.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_EDITOR', 'ROLE_ADMIN')")
public @interface IsEditor {
}

IsReviewer.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_REVIEWER', 'ROLE_ADMIN')")
public @interface IsReviewer {
}

IsAdmin.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public @interface IsAdmin { 
}

Spring Security自帶表達(dá)式

  • hasRole(),是否擁有某一個(gè)權(quán)限

  • hasAnyRole()阻塑,多個(gè)權(quán)限中有一個(gè)即可,如 hasAnyRole("ADMIN","USER")

  • hasAuthority()果复,AuthorityRole 很像陈莽,唯一的區(qū)別就是 Authority 前綴多了 ROLE_ ,如 hasAuthority("ROLE_ADMIN") 等價(jià)于 hasRole("ADMIN") 虽抄,可以參考上面 IsUser.java 的寫法

  • hasAnyAuthority()走搁,同上,多個(gè)權(quán)限中有一個(gè)即可

  • permitAll(), denyAll(),isAnonymous(), isRememberMe()迈窟,通過(guò)字面意思可以理解

  • isAuthenticated(), isFullyAuthenticated()私植,這兩個(gè)區(qū)別就是isFullyAuthenticated()對(duì)認(rèn)證的安全要求更高。例如用戶通過(guò)記住密碼功能登入到系統(tǒng)進(jìn)行敏感操作车酣,isFullyAuthenticated()會(huì)返回false曲稼,此時(shí)我們可以讓用戶再輸入一次密碼以確保安全,而 isAuthenticated() 只要是登入用戶均返回true湖员。

  • principal(), authentication()贫悄,例如我們想獲取登入用戶的id,可以通過(guò)principal() 返回的 Object 獲取娘摔,實(shí)際上 principal() 返回的 Object 基本上可以等同我們自己編寫的 CustomUser 窄坦。而 authentication() 返回的 AuthenticationPrincipal 的父類,相關(guān)操作可看 Authentication 的源碼凳寺。進(jìn)一步了解可以看后面Controller編寫中獲取用戶數(shù)據(jù)的四種方法

  • hasPermission()鸭津,參考字面意思即可

如果想進(jìn)一步了解,可以參考 Intro to Spring Security Expressions肠缨。

添加Thymeleaf支持

我們通過(guò) thymeleaf-extras-springsecurity 來(lái)添加Thymeleaf對(duì)Spring Security的支持逆趋。

Maven配置

上面的Maven配置已經(jīng)加過(guò)了

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

使用例子

注意我們要在html中添加 xmlns:sec 的支持

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>Admin</title>
</head>
<body>
<p>This is a home page.</p>
<p>Id: <th:block sec:authentication="principal.id"></th:block></p>
<p>Username: <th:block sec:authentication="principal.username"></th:block></p>
<p>Role: <th:block sec:authentication="principal.authorities"></th:block></p>
</body>
</html>

如果想進(jìn)一步了解請(qǐng)看文檔 thymeleaf-extras-springsecurity

Controller編寫

IndexController.java

本控制器沒有任何的權(quán)限規(guī)定

@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index/index";
    }

    @GetMapping("/login")
    public String login() {
        return "index/login";
    }

    @GetMapping("/logout")
    public String logout() {
        return "index/logout";
    }
}

UserController.java

在這個(gè)控制器中怜瞒,我綜合展示了自定義注解的使用和4種獲取用戶信息的方式

@IsUser // 表明該控制器下所有請(qǐng)求都需要登入后才能訪問(wèn)
@Controller
@RequestMapping("/user")
public class UserController {

    @GetMapping("/home")
    public String home(Model model) {
        // 方法一:通過(guò)SecurityContextHolder獲取
        CustomUser user = (CustomUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        model.addAttribute("user", user);
        return "user/home";
    }

    @GetMapping("/editor")
    @IsEditor
    public String editor(Authentication authentication, Model model) {
        // 方法二:通過(guò)方法注入的形式獲取Authentication
        CustomUser user = (CustomUser)authentication.getPrincipal();
        model.addAttribute("user", user);
        return "user/editor";
    }

    @GetMapping("/reviewer")
    @IsReviewer
    public String reviewer(Principal principal, Model model) {
        // 方法三:同樣通過(guò)方法注入的方法父泳,注意要轉(zhuǎn)型,此方法很二吴汪,不推薦
        CustomUser user = (CustomUser) ((Authentication)principal).getPrincipal();
        model.addAttribute("user", user);
        return "user/reviewer";
    }

    @GetMapping("/admin")
    @IsAdmin
    public String admin() {
        // 方法四:通過(guò)Thymeleaf的Security標(biāo)簽進(jìn)行惠窄,詳情見admin.html
        return "user/admin";
    }
}

注意

  • 如果有安全控制的方法 A 被同一個(gè)類中別的方法調(diào)用,那么方法 A 的權(quán)限控制會(huì)被忽略漾橙,私有方法同樣會(huì)受到影響
  • Spring 的 SecurityContext 是線程綁定的杆融,如果我們?cè)诋?dāng)前的線程中新建了別的線程,那么他們的 SecurityContext 是不共享的霜运,進(jìn)一步了解請(qǐng)看 Spring Security Context Propagation with @Async

Html的編寫

在編寫html的時(shí)候脾歇,基本上就是大同小異了蒋腮,就是注意一點(diǎn),**如果開啟了CSRF藕各,在編寫表單POST請(qǐng)求的時(shí)候添加上隱藏字段池摧,如 **<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> ,不過(guò)大家其實(shí)不用加也沒事激况,因?yàn)門hymeleaf自動(dòng)會(huì)加上去的??作彤。

總結(jié)

教程粗糙,歡迎指正乌逐!

如需深入了解竭讳,如果想系統(tǒng)的學(xué)習(xí)可以看看 Security with Spring

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浙踢,一起剝皮案震驚了整個(gè)濱河市绢慢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洛波,老刑警劉巖胰舆,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奋岁,居然都是意外死亡思瘟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門闻伶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滨攻,“玉大人,你說(shuō)我怎么就攤上這事蓝翰」馊疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵畜份,是天一觀的道長(zhǎng)诞帐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)爆雹,這世上最難降的妖魔是什么停蕉? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮钙态,結(jié)果婚禮上慧起,老公的妹妹穿的比我還像新娘。我一直安慰自己册倒,他們只是感情好蚓挤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般灿意。 火紅的嫁衣襯著肌膚如雪估灿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天缤剧,我揣著相機(jī)與錄音馅袁,去河邊找鬼。 笑死荒辕,一個(gè)胖子當(dāng)著我的面吹牛司顿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兄纺,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼化漆!你這毒婦竟也來(lái)了估脆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤座云,失蹤者是張志新(化名)和其女友劉穎疙赠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朦拖,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡圃阳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了璧帝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捍岳。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖睬隶,靈堂內(nèi)的尸體忽然破棺而出锣夹,到底是詐尸還是另有隱情,我是刑警寧澤苏潜,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布银萍,位于F島的核電站,受9級(jí)特大地震影響恤左,放射性物質(zhì)發(fā)生泄漏贴唇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一飞袋、第九天 我趴在偏房一處隱蔽的房頂上張望戳气。 院中可真熱鬧,春花似錦授嘀、人聲如沸物咳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)览闰。三九已至芯肤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間压鉴,已是汗流浹背崖咨。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留油吭,地道東北人击蹲。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像婉宰,于是被迫代替她去往敵國(guó)和親歌豺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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