Apache Shiro

Apache Shiro

Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。

Apache Shiro Features

Shiro 把 Shiro 开发团队称为“应用程序的四大基石”——身份验证,授权,会话管理和加密作为其目标。

  • Authentication:简称为"登录",这是一个证明用户是他们所说的他们是谁的行为
  • Authorization:访问空值的过程,也就是"谁"去访问"什么";
  • Session Management:管理用户特定的会话, 即使在非 Web 或 EJB 应用程序
  • Cryptography:通过使用加密算法保持数据安全同时易于使用

开始一个应用程序

public static void main(String[] args){
  //1.
  Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
  //2.
  SecurityManager securityManager = factory.getInstance();
  //3.
  SecurityUtils.setSecurityManage(securityManger);
  
  System.exit(0);
}

在几乎所有环境中,都可以通过下面的调用获取当前正在执行的用户:

Subject currentUser = SecurityUtils.getSubject();   

subject指的是:"当前正在执行的用户的特定的安全视图",可以把Subject看成是shiro的"User"概念

subject获取会话

Session session = currentUser.getSession();
session.setAttribute("someKye","aValue");

为已知用户做检查

if(!currentUser.isAuthenticated()){
  UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
  token.setRememberMe(true);
  currentUser.login(token);
}

如果登陆失败则可以进行异常捕获

try{
  currentUser.login(token);
}catch(UnknownAccountException u){
  //username wasn't in the system ,show them an error message?
}catch(IncorrectCredentitalsException e){
  //password didn't match ,try again?
}catch(LockedAccountException l){
  //account for that username is locked -can't login.
}

获取登陆的角色

log.info("User ["+currentUser.getPrincipal()+"] logged in successfully.");

测试是否有特定的角色:

if(currentUser.hasRole("luoluo")){
  log.info("May the luoluo be with you ");
}else{
  log.info("hello ,mere mortal.");      
}

判断是否有权限在一个确定类型实体上进行操作:

if(currentUser.isPermitted("lightsaber:weild")){
  log.info("You may use a lightsaber ring. Use it wisely.");
}else{
  log.info("Sorry,lightsaber rings are for schwartz masters only");
}

注销

currentUser.logout();

完整事例:

public class Tutorial{
  private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
  
  public static void main(String[] args){
    log.info("My First Apache Shiro Application");
    //加载用户信息
    Factory<SecurityManager> factory = new IniSecurityManagerFacotry("classpath:shiro:ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    
    //get the currently executing user;
    Subject currentUser = SecurityUtils.getSubject();
    
    //Do some stuff with a session(no need for a web or EJB container!!!)
    Session session = currentUser.getSession();
    session.setAttribute("someKey","aValue");
    String value = (String)session.getAttribute("someKey");
    if(value.equals("aValue")){
      log.info("Retrieved the corrent value!["+value+"]");
    }
    
    //let's login the current user so we can check against and permissions;
    if(!currentUser.isAuthenticated()){
      UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
      token.setRememberMe(true);
      try{
        currentUser.login(token);
      }catch(UnknownAccountException u){
        log.info("There is no user with username of "+token.getPrincipal());
      }catch(IncorrentCredentitalsException i){
        log.info("Password for account "+token.getPrincipal()+" was incorrect!");
      }catch(LockedAccountException l){
        
      }
    }
    
    //sya who they are:
    //print their identitfying principal(in this case,a username)
    log.info("User["+currentUser.getPrincipal()+"]logged in successfully.");
    
    //test a role ;
    if(currentUser.hasRole("luoluo")){
      log.info("May the luoluo be with you ");
    }else{
      log.info("hello ,mere mortal");
    }
    
    //test a typed permission(not instance-level)
    if(currentUser.isPermitted("lightsaber:weild")){
      log.info("you may use a lightsaber:weild");
    }else{
      log.info("Sorry , lightsaber rings are for schwartz masters only.");
    }
    
    //log_out!
    currentUser.logout();
    
    System.exit(0);
  }
}

Apache Shiro Architecture

shiro的架构有3个主要的概念:Subject ,SecurityManager 和 Realms

  • Subject:一个 Subject 可以是一个人,但它还可以代表第三方服务或其他类似的任何东西——基本上是当前正与软件进行交互的任何东西
  • SecurityManager:是Shiro架构的心脏,并作为一种"保护伞"对象来协调内部的安全组件共同构成一个对象图
  • Realms::Realms 担当 Shiro 和你的应用程序的安全数据之间的“桥梁”或“连接器”。当它实际上与安全相 关的数据如用来执行身份验证(登录)及授权(访问控制)的用户帐户交互时, Shiro 从一个或多个为应用程 序配置的 Realm 中寻找许多这样的东西。

Apache Shiro Configuration

首先需要创建一个SecurityManager

Realm realm = //instantiate or acquire a Realm instance.

SecurityManager securityManger = new DefaultSecurityManager(realm);

//Make the SecurityManager instance available to the entire application via static memory;
SecurityUtils.setSecurityManager(securityManager);

Authenticating Subjects(验证Subjects)

可以大致分为三步:

  1. 收集Subjects提交的Principals(身份)和Credentials(凭证)
  2. 提交Principals(身份)和Credentials(凭证)进行身份验证
  3. 如果提交成功,则允许访问,否则重新进行身份验证

Set1:

UsernamePasswordToken token = new UsernamePasswordToken(username,password);

token.setRememberMe(true);

Step2:

Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);

通过login方法,有效的体现了身份验证

Step3:处理成功或失败

如果验证成功,则调用subject.isAuthenticated()的调用将返回true

验证失败则运行AuthenticationException

try{
  curretnUser.login(token);
}catch(UnknownAccountException uae){
  
}catch(IncorrectCredentialsException ice){
  
}catch(LockedAccountException eas){
  
}catch(ExcessiveAttemptsException eae){
  
}catch(AuthenticationException ae){
  
}

AuthenticationStrategy

当一个应用程序配置了两个或两个以上的 Realm 时,ModularRealmAuthenticator 依靠内部的 AuthenticationStrategy 组件来确定这些认证尝试的成功或失败条件

AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的 状态将被作为方法参数):

  1. 在任何Realm被调用之前被询问
  2. 在一个单独的Realm的getAuthenticationInfo方法被调用之前立即被询问
  3. 在一个单独的Realm的getAuthenticationInfo方法被调用之后立即被询问
  4. 在所有Realm被调用之后被询问

Shiro 有 3 个具体的 AuthenticationStrategy 实现:

AtLeastOneSuccessfulStrategy:如果一个(或更多)Realm 验证成功,则整体的 尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。

FirstSuccessfulStrategy:只有第一个成功地验证的 Realm 返回的信息将被 使用。所有进一步的 Realm 将被忽略。如果没有 一个验证成功,则整体尝试失败。

AllSucessfulStrategy为了整体的尝试成功,所有配置的 Realm 必须验 证成功。如果没有一个验证成功,则整体尝试失败。

Authorization(授权)

控制谁有权限在应用程序中做什么

在 Shiro 中执行授权可以有 3 种方式:

  • 编写代码——你可以在你的 Java 代码中用像 if 和 else 块的结构执行授权检查
  • JDK 的注解——你可以添加授权注解给你的 Java 方法
  • JSP/GSP 标签库——你可以控制基于角色和权限的 JSP 或者 GSP 页面输出

Role-Based Authorization(基于角色的授权)

Role checks

Subject currentUser = SecurityUtils.getSubject();

if(currentUser.hasRole("administrator")){
  //show the admin button
}else{
  //don't show the button?Grey it out?
}

Role Assertions(角色断言)

Subject currentUser = SecurityUtils.getSubject();
//guarantee that the current user is a bank teller and therefore allowed to open the account:
currentUser.checkRole("bankTeller");
openBankAccount();

通过使用 hasRole*方法的一个好处就是代码可以变得清洁,由于你不需要创建你自己的 AuthorizationException 如果 当前的 Subject 不符合预期条件

Permission-Based Authorization(基于权限的授权)

Permission Checks(权限检查)

1.基于对象

Permission printPermission = new PrinterPermission("laserjet4400h","print");
Subject currentUser = SecurityUtils.getSubject();
if(currentUser.isPermitted(printPermission)){
  //show the Print button
}else{
  //don't show the button?
}

2.基于字符串

Subject currentUser = SecurityUtils.getSubject();
if(currentUser.isPermitted("printer:print:laserjet4400n")){
  //TODO
}else{
  //TODO
}

基于字符串的权限是很有帮助的,由于你不必被迫实现一个接口,而且简单的字符串易于阅读。其缺点是,你不具 备类型安全,如果你需要更为复杂的行为将超出了字符串所能代表的范围,你就得实现你自己的基于权限接口的权 限对象。

Permission Assertions(权限断言)

Subject currentUser = SecurityUtils.getSubject();

Permission p = new AccountPersion("open");
currentUser.checkPermission(p);
openBankAccount();

或者,使用字符串权限:

Subject currentUser = SecurityUtils.getSubject();

currentUser.checkPermission("open");
openBankAccount();

Wildcard Permissions

为了使用易于处理且仍然可读的权限语句,Shiro 提供了强大而直观的语法,我们称之为 WildcardPermission。

一个极其简单的方法是授予用户"queryPrinter"权限。然后你可以检查用户是否具有 queryPrinter 权限通过调用:

subject.isPermitted("queryPrinter");

//上面的语法等同于
subject.isPermitted(new WildcardPermission("queryPrinter"));

Multiple Parts

通配符权限支持多层次或部件(parts)的概念

在该例中,第一部分是权限被操作的领域(打印机),第二部分是被执行的操作(查询)。上面其他的例子将被改 为:

printer:print
printer:manage

Multiple Vaules

printer:print,query

它能够赋予用户 print 和 query 打印机的能力。由于他们被授予了这两个操作,你可以通过调用下面的语句来判断用 户是否有能力查询打印机:

subject.isPermitted("print:query");//返回true

All Values

printer:query,print,manage

简单点:

printer:*

最后,在一个通配符权限字符串中的任何部分使用通配符 token 也是可以的。例如,如果你想对某个用户在所有领 域(不仅仅是打印机)授予"view"权限,你可以这样做:

*:view

Instance-Level Access Control

另一种常见的通配符权限用法是塑造实例级的访问控制列表。在这种情况下,你使用三个部件——第一个是域,第 二个是操作,第三个是被付诸实施的实例。

printer:query:lp7200
printer:print:epsoncolor

第一个定义了查询拥有 ID lp7200 的打印机的行为。第二条权限定义了打印到拥有 ID epsoncolor 的打印机的行为。

如果你授予这些权限给用户,那么他们能够在特定的实例上执行特定的行为。然后你可以在代码中做一个检查:

if(SecurityUtils.getSubject().isPermitted("printer:query:lp7200")){
  //Return the current jobs on printer lp 7200
}

Missing Parts

printer:print

等价于

printer:print:*
printer

等价于

printer:*.*

Apache Shiro Realms

Realm 是一个能够访问应用程序特定的安全数据(如用户、角色及权限)的组件。

Handling supported AuthenticationTokens

若 Realm 支持一个提交的 AuthenticationToken,那么 Authenticator 将会调用该 Realm 的 getAuthenticationInfo(token) 方法。这有效地代表了一个与 Realm 的后备数据源的授权尝试。该方法按以下方法进行:

  1. 为主要的识别信息检查token
  2. 基于principal在数据源中寻找相吻合的账户数据
  3. 确保token支持的credentials匹配那些存储在数据源的
  4. 若credentitals匹配,返回一个封装了Shiro能够理解的账户数据格式的AuthenticationInfo实例
  5. 若credentials不匹配,则抛出AuthenticationException异常

Session Management

Apache Shiro 提供安全框架界独一无二的东西:一个完整的企业级 Session 解决方案,从最简单的命令行及智能手机 应用到最大的集群企业 Web 应用程序。

即使你在一个 Servlet 或 EJB 容器中部署你的应用程序,仍然有令人信服的理由来使用 Shiro 的 Session 支持而不是容 器的。下面是一个 Shiro 的 Session 支持的最可取的功能列表:

  • pojo/j2sebased:Shiro 的一切(包括所有 Session 和 Session Management 方面)都是基于接口和 POJO 实现
  • Easy Custom Session Storage:因为 Shiro 的 Session 对象是基于 POJO 的,会话数据可以很容易地存储在任意 数量的数据源。
  • Container-Independent Clustering:Shiro 的会话可以很容易地聚集通过使用任何随手可用的网络缓存产品,像 Ehcache + Terracotta,Coherence,GigaSpaces, 等等。
  • Heterogeneous Client Access:与 EJB 或 web 会话不同, Shiro 会话可以被各种客户端技术“共享”。
  • Event Listeners:事件监听器允许你在会话生命周期监听生命周期事件。你可以侦听这些事件和对自定义应用 程序的行为作出反应——例如,更新用户记录当他们的会话过期时。
  • Host Address Retention:Shiro Sessions 从会话发起地方保留 IP 地址或主机名。这允许你确定用户所在,并作 出相应的反应(通常是在 IP 分配确定的企业内部网络环境)。
  • Inactivity/Expiration Support:由于不活动导致会话过期如预期的那样,但它们可以延续很久通过 touch()方法 来保持它们“活着”,如果你希望的话。
  • Transparent Web Use:Shiro的网络支持
  • Can be used for SSO:由于 Shiro 会话是基于 POJO 的,它们可以很容易地存储在任何数据源,而且它们可以跨 程序“共享”如果需要的话。

使用方式:

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey","someValue");

JSP Tag Library

引入:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

The guest tag

guest 标签将显示它包含的内容,仅当当前的 Subject 被认为是'guest'时。'guest'是指没有身份 ID 的任何 Subject。也 就是说,我们并不知道用户是谁

<shiro:guest>
    Hi there ! Please<a href = "login.jsp">Login</a>or<a href="signup.jsp">Signup</a>today!
</shiro:guest>

The user Tag

user 标签将显示它包含的内容,仅当当前的 Subject 被认为是'user'时。'user'在上下文中被定义为一个已知身份 ID 的 Subject,或是成功通过身份验证及通过'RememberMe'服务的。请注意这个标签在语义上与 authenticated 标签是 不同的,authenticated 标签更为严格。

<shiro:user>
  Welcome back luoluo ! NOt luoluo? Click<a href="login.jsp">here<a>to login.
</shiro:user>  

The authenticated tag

仅仅只当当前用户在当前会话中成功地通过了身份验证 authenticated 标签才会显示包含的内容。它比'user'标签更 为严格。

<shiro:authenticated>
  <a href="updateAccount.jsp">Update your contact information</a>
</shiro:authenticated>

The principal tag

principal 标签将会输出 Subject 的主体(标识属性)或主要的属性。

Hello,<shiro:principal />,how are you today?

等价于

Hello,<%=SecurityUtils.getSubject().getPrincipal().toString() %>,how are you today?

Typed principal

principal 标签默认情况下,假定该 principal 输出的是 subject.getPrincipal()的值。但若你想输出一个不是主要 principal 的值,而是属于另一个 Subject 的 principal collection,你可以通过类型来获取该 principal 并输出该值。

User ID:<principal type="java.lang.Integer" />

等价于

User ID:<%=SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString()%>

Principal property

但如果该 principal(是默认主要的 principal 或是上面的'typed' principal)是一个复杂的对象而不是一个简单的字符串, 而且你希望引用该 principal 上的一个属性该怎么办呢?你可以使用 property 属性来来表示 property 的名称来理解 (必须通过 JavaBeans 兼容的 getter 方法访问)。

Hello,<shiro:principal property="firstName" />,how are you today

等价于

Hello,<%=SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>,how are you today?

或者结合属性

Hello,<shiro:principal type="com.foo.User" property="firstName" /> ,how are you today?

等价于

Hello ,<%=SecurityUtils.getSubject().getPrincipals.oneByType(com.foo.User.class).getFirstName().toString() %>,how are you today?

The hasRole tag

<shiro:hasRole name="administrator">
  <a href="admin.jsp">Administer the system</a>
</shiro:hasRole>

The hasAnyRole tag

<shiro:hasAnyRole name="developer,project manager , administrator">
    You are either a developer,project manager,or administrater.
</shiro:hasAnyRole>

The hasPermission tag

hasPermission 标签将会显示它所包含的内容,仅当当前 Subject“拥有”(蕴含)特定的权限。也就是说,用户具 有特定的能力。

<shiro:hasPermission name="user:create">
    <a href="createUser.jsp">Create a new User</a>
</shiro:hasPermission>

The lacksPermission tag

lacksPermission 标签将会显示它所包含的内容,仅当当前 Subject 没有拥有(蕴含)特定的权限。也就是说,用户没 有特定的能力。

<shiro:lacksPermission name="user:delete">
    Sorry,you are not allowed to deleted user accounts;
</shiro:lacksPermission>

Understanding Subjects in Apache Shiro

一个 Shiro Subject 实例代表了一个单一应用程序用户的安全状态和操作

这些操作包括:

  • authentication(login)
  • authorization(access control)
  • session access
  • logout

The Currently Executing Subject

getSubject()方法调用一个独立的应用程序,该应用程序可以返回一个在应用程序特有位置上基于用户数据的 Subject, 在服务器环境中(如,Web 应用程序),它基于与当前线程或传入的请求相关的用户数据上获得 Subject。

Subject currentUser = SecurityUtils.getSubject();

可得的他们的 session:

Session session = currentUser.getSession();
session.setAttribute("someKey","aValue");

可以获取用户信息

log.info("User["+currentUser.getPrincipal()+"]logged in successfully");

可以测试是否有特定的角色

if(currentUser.hasRole("schwartz")){
  log.info(".....");
}else{
  log.info(".....");
}

可以判断否有权限

if(currentUser.isPermitted("...")){
  
}else{
  
}

可以注销

currentUser.logout();

Spring Framework

这里是在 Spring 应用程序中启用应用程序单例 SecurityManager 的最简单的方法:

web.xml

 <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       
    xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:context="http://www.springframework.org/schema/context"   
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans.xsd    
    http://www.springframework.org/schema/aop    
    http://www.springframework.org/schema/aop/spring-aop.xsd    
    http://www.springframework.org/schema/tx    
    http://www.springframework.org/schema/tx/spring-tx.xsd    
    http://www.springframework.org/schema/context    
    http://www.springframework.org/schema/context/spring-context.xsd">
    
    <description>Shrio的配置文件</description>
    
    <!-- SecurityManager配置 -->
    <!-- 配置Realm域 -->
    <!-- 密码比较器 -->
    <!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器-->
    <!-- 配置缓存:ehcache缓存 -->
    <!-- 安全管理 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
        <!-- 缓存 -->
        <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean>

    <!-- 自定义权限认证 -->
    <bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm">
        <property name="userService" ref="userService"/>
        <!-- 自定义密码加密算法  -->
        <property name="credentialsMatcher" ref="passwordMatcher"/>
    </bean>
    
    <!-- 设置密码加密策略 md5hash -->
    <bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/>

    <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--登录页面  -->
        <property name="loginUrl" value="/index.jsp"></property>
        <!-- 登录成功后 -->      
        <property name="successUrl" value="/home.action"></property>
        
        
        <property name="unauthorizedUrl" value="/index.jsp"></property>
        
        <property name="filterChainDefinitions">
            <!-- /**代表下面的多级目录也过滤 -->
            <value>
                /index.jsp* = anon
                /home* = anon
                /sysadmin/login/login.jsp* = anon
                /sysadmin/login/logout.jsp* = anon
                /login* = anon
                /logout* = anon
                /components/** = anon
                /css/** = anon
                /images/** = anon
                /js/** = anon
                /make/** = anon
                /skin/** = anon
                /stat/** = anon
                /ufiles/** = anon
                /validator/** = anon
                /resource/** = anon
                /sysadmin/deptAction_* = perms["部门管理"]
                /** = authc
                /*.* = authc
            </value>
        </property>
    </bean>

    <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 生成代理,通过代理进行控制 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    
    <!-- 安全管理器 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
</beans>

如果需要设置注解,则要添加如下配置

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
</bean>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容