关于Shiro框架的学习(二)

前言

接上篇,关于Shiro框架的学习(一),这篇会记录下Shiro整合Web、整合SSM的过程,之后就可以直接应用在项目的安全控制上。

关于整合Web

  • 环境

    Eclipse、MySQL、Tomcat8

  • 准备工作

    • 创建Dynamic Web工程:


      Dynamic工程
    • 使用到的类
      准备User实体类、ShiroDao类、DatabaseRealm类,这三个类在上一篇文章中已经提及,这里不再重复赘述。

    • 数据库
      沿用上一篇博文中未加密的数据库,数据库脚本上一篇已提及,同样不再重复贴代码了。

    • jar包:
      本次需要用到的jar包主要有如下几个


      jar
  • 修改shiro.ini配置文件和web.xml配置文件

    配置文件中指定了寻找DatabaseRealm的方法、指定了每个页面需要什么角色和权限、指定了如果没有权限将会跳转到哪个页面。

    [main] 
    #使用数据库进行验证和授权
    databaseRealm=com.shiro.DatabaseRealm
    securityManager.realms=$databaseRealm
    
    #当访问需要验证的页面,但是又没有验证的情况下,跳转到login.jsp
    authc.loginUrl=/login.jsp
    #当访问需要角色的页面,但是又不拥有这个角色的情况下,跳转到noroles.jsp
    roles.unauthorizedUrl=/noRoles.jsp
    #当访问需要权限的页面,但是又不拥有这个权限的情况下,跳转到noperms.jsp
    perms.unauthorizedUrl=/noPerms.jsp
    
    #users,roles和perms都通过前面知识点的数据库配置了
    [users] 
    
    #urls用来指定哪些资源需要什么对应的授权才能使用
    [urls] 
    #doLogout地址就会进行退出行为
    /doLogout=logout
    #login.jsp,noroles.jsp,noperms.jsp 可以匿名访问
    /login.jsp=anon
    /noroles.jsp=anon
    /noperms.jsp=anon
    
    #阅读博客,需要登录后才可以查看
    /readBlog.jsp=authc 
    #新增博客不仅需要登录,而且要拥有 blogManager 角色才可以操作
    /addBlog.jsp=authc,roles[blogManager] 
    #删除博客,不仅需要登录,而且要拥有 deleteBlog 权限才可以操作
    /deleteBlog.jsp=authc,perms["deleteBlog"]  
    

    web.xml配置了加载shiro.ini的配置
    <web-app>中配置

    <listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>
    <context-param>
      <param-name>shiroEnvironmentClass</param-name>
      <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
      <!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini -->
    </context-param>
    <context-param>
      <param-name>shiroConfigLocations</param-name>
      <param-value>classpath:shiro.ini</param-value>
    </context-param>
    <filter>
      <filter-name>shiroFilter</filter-name>
      <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>shiroFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  • Servlet

    新建一个LoginServlet,负责控制登录验证。

    @WebServlet(name = "loginServlet", urlPatterns = "/login") 
    public class LoginServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name"); 
    String password = request.getParameter("password"); 
    Subject subject = SecurityUtils.getSubject(); 
    UsernamePasswordToken token = new UsernamePasswordToken(name, password); 
    try { 
      subject.login(token);
            //通过subject获取session
      Session session=subject.getSession();
      session.setAttribute("subject", subject);
      response.sendRedirect("");
    }catch (AuthenticationException e) { 
      request.setAttribute("error", "验证失败"); 
      request.getRequestDispatcher("login.jsp").forward(request, response);
            } 
        }
    }
    
  • Jsp前台页

    • index.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    
    </head>
    <body>
    
    <div class="workingroom">
        <div class="loginDiv">
        <c:if test="${empty subject.principal}">
            <a href="login.jsp">登录</a><br>
        </c:if>
        <c:if test="${!empty subject.principal}">
            <span class="desc">你好,${subject.principal},</span>
            <a href="doLogout">退出</a><br>
        </c:if>
        <a href="readBlog.jsp">查看博客</a><span class="desc">(登录后才可以查看) </span><br>
        <a href="addBlog.jsp">新增博客</a><span  class="desc">(要有博客管理员角色, Reader是读者,Object是博客管理员) </span><br>
        <a href="deleteBlog.jsp">删除博客</a><span class="desc">(要有删除订单权限, Object有该权限) </span><br>
    </div>
    
    </body>
    </html>
    
    • login.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
    
    <!DOCTYPE html>
    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    
    <div class="workingroom">
    
    <div class="errorInfo">${error}</div>
        <form action="login" method="post">
            账号: <input type="text" name="name"> <br>
            密码: <input type="password" name="password"> <br>
            <br>
            <input type="submit" value="登录">
            <br>
            <br>
            <div>
                <span class="desc">账号:Object 密码:123456 角色:blogManager</span><br>
                <span class="desc">账号:Reader 密码:654321 角色:reader</span><br>
            </div>
        </form>
    </div>
    
    • readBlog.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
    
    <!DOCTYPE html>
    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    <div class="workingroom">
        readBlog.jsp ,能进来,就表示已经登录成功了
        <br>
        <a href="#" onClick="javascript:history.back()">返回</a>
    </div>
    
    • addBlog.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
    
    <!DOCTYPE html>
    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    
    <div class="workingroom">
        addBlog.jsp,能进来<br>就表示拥有 blogManager 角色
        <br>
        <a href="#" onClick="javascript:history.back()">返回</a>
    </div>
    
    • deleteBlog.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
    <!DOCTYPE html>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    
    <div class="workingroom">
        deleteBlog.jsp ,能进来,就表示有deleteBlog权限
        <br>
        <a href="#" onClick="javascript:history.back()">返回</a>
    </div>
    
    • noRoles.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
    <!DOCTYPE html>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    <div class="workingroom">
        角色不匹配
        <br>
        <a href="#" onClick="javascript:history.back()">返回</a>
    </div>
    
    • noPerms.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8" import="java.util.*"%>
    <!DOCTYPE html>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="static/css/style.css" />
    <div class="workingroom">
        权限不足
        <br>
        <a href="#" onClick="javascript:history.back()">返回</a>
    </div>
    
    • style.css(页面样式)
    span.desc{
    margin-left:20px;
    color:gray;
    }
    div.workingroom{
            margin:200px auto;
            width:400px;
    }
    div.workingroom a{
            display:inline-block;
            margin-top:20px;
    }
    div.loginDiv{
            text-align: left;
    }
    div.errorInfo{
            color:red;
            font-size:0.65em;
    }
    
  • 测试

打开tomcat服务器,在浏览器url输入:localhost:8080/ShiroWeb


登录界面

登录Object


登录成功

点击各功能:


登录访问成功
访问成功
权限访问成功

登录Reader后点击各功能:
除了ReadBlog可以进以外,其余都失败


登录访问成功
权限访问失败
角色访问失败

关于整合SSM

  • 使用@RequireRoles注解

核心:


SSM注解
这种方式只需要在Controller映射页面的方法上加上@RequireRoles("需要的权限")就可以轻松控制页面所需的权限了,但是在真实开发中,如果权限改变,那么你就需要一直去修改源代码,这样显然不合适,所以这种方式一带而过,可以在这里了解:[Shiro系列教材](http://how2j.cn/k/shiro/shiro-plan/1732.html)。
  • 使用基于URL配置权限

    这种方式主要是不用在每个页面的映射上都加所需的权限,而是动态地将角色信息和权限信息写入数据库,再读取数据库,看页面是否需要拦截,访问页面需要什么权限等。

    • 从零搭建
      首先先修改表结构

      DROP DATABASE IF EXISTS shiro;
      CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
      USE shiro;
      
      drop table if exists user;
      drop table if exists role;
      drop table if exists permission;
      drop table if exists user_role;
      drop table if exists role_permission;
      
      create table user (
          id bigint auto_increment,
          name varchar(100),
          password varchar(100),
          salt varchar(100),
          constraint pk_users primary key(id)
      ) charset=utf8 ENGINE=InnoDB;
      
      create table role (
          id bigint auto_increment,
          name varchar(100),
          desc_ varchar(100),
          constraint pk_roles primary key(id)
      ) charset=utf8 ENGINE=InnoDB;
      
      create table permission (
          id bigint auto_increment,
          name varchar(100),
          desc_ varchar(100),
          url varchar(100), 
          constraint pk_permissions primary key(id)
      ) charset=utf8 ENGINE=InnoDB;
      
      create table user_role (
          id bigint auto_increment,
          uid bigint,
          rid bigint,
          constraint pk_users_roles primary key(id)
      ) charset=utf8 ENGINE=InnoDB;
      
      create table role_permission (
          id bigint auto_increment,
          rid bigint,
          pid bigint,
          constraint pk_roles_permissions primary key(id)
      ) charset=utf8 ENGINE=InnoDB;
      

      实际上上面这段代码就是对原来的表新增了几个字段,新增字段如下:
      permission:desc_url
      role:desc_

      插入表数据:

      INSERT INTO `permission` VALUES (1,'addblog','新增博客','/addBlog');
      INSERT INTO `permission` VALUES (2,'readerBlog','阅读博客','/readBlog');
      INSERT INTO `role` VALUES (1,'blogManager','博客管理员');
      INSERT INTO `role` VALUES (2,'reader','读者');
      INSERT INTO `role_permission` VALUES (1,1,1);
      INSERT INTO `role_permission` VALUES (2,2,2);
      INSERT INTO `user` VALUES (1,'Object','a7d59dfc5332749cb801f86a24f5f590','e5ykFiNwShfCXvBRPr3wXg==');
      INSERT INTO `user` VALUES (2,'Reader','43e28304197b9216e45ab1ce8dac831b','jPz19y7arvYIGhuUjsb6sQ==');
      INSERT INTO `user_role` VALUES (1,2,2);
      INSERT INTO `user_role` VALUES (2,1,1);
      
    • 配置文件web.xml
      在web.xml文件中要配置四块内容,分别是,中文过滤器Spring相关MVC相关Shiro过滤器相关
      先说说shiro过滤器

    <!-- Shiro-->
    <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>
    

    shiro过滤器默认拦截所有请求

    Spring相关:主要有两个,一个是Spring整合Mybatis,一个是Spring整合Shiro

     <!-- spring -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml,
            classpath:applicationContext-shiro.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    MVC相关:
    就是日常的 MVC配置

     <!-- spring mvc -->
      <servlet>
          <servlet-name>mvc-dispatcher</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!-- spring mvc-->
          <init-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath:springMVC.xml</param-value>
          </init-param>
          <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
          <servlet-name>mvc-dispatcher</servlet-name>
          <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    • Spring中关于Shiro的配置:
      1.SecurityManager:Shiro的核心安全管理类。
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="databaseRealm" />
        <property name="sessionManager" ref="sessionManager" />
    </bean>
    <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
    <bean
        class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod"
            value="org.apache.shiro.SecurityUtils.setSecurityManager" />
        <property name="arguments" ref="securityManager" />
    </bean>
    

    2.HashedCredentialsMatcher:密码匹配器,可散列

     <!-- 密码匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="2"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>
    

    3.LifecycleBeanPostProcessor:保证了Shiro内部lifecycle函数的执行

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

    4.ShiroFilterFactoryBean:shiro的过滤器工厂类

     <!-- url过滤器 -->          
    <bean id="urlPathMatchingFilter" class="com.shiro.filter.URLPathMatchingFilter"/>
         
    <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 调用我们配置的权限管理器 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 配置我们的登录请求地址 -->
        <property name="loginUrl" value="/login" />
        <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 -->
        <property name="unauthorizedUrl" value="/unauthorized" />
        <!-- 退出 -->
        <property name="filters">
            <util:map>
                <entry key="logout" value-ref="logoutFilter" />
                <entry key="url" value-ref="urlPathMatchingFilter" />
            </util:map>
        </property>
        <!-- 权限配置 -->
        <property name="filterChainDefinitions" ref="filterChainDefinitions"/>
    </bean>
    <!-- 退出过滤器 -->
    <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="/index" />
    </bean>
    

    5.FilterChainDefinitions:配置可从数据库中读取页面权限的LinkedHashMap

    <bean id="filterChainDefinitions" factory-bean="filterChainDefinitionsFactory" factory-method="buildFilterChainDefinitionMap"></bean>
    <bean id="filterChainDefinitionsFactory" class="com.shiro.entity.FilterChainDefinitions">
    </bean>
    

    6.会话相关

    <!-- 会话ID生成器 -->
    <bean id="sessionIdGenerator"
        class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" />
    <!-- 会话Cookie模板 关闭浏览器立即失效 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid" />
        <property name="httpOnly" value="true" />
        <property name="maxAge" value="-1" />
    </bean>
    <!-- 会话DAO -->
    <bean id="sessionDAO"
        class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="sessionIdGenerator" ref="sessionIdGenerator" />
    </bean>
    <!-- 会话验证调度器,每30分钟执行一次验证 ,设定会话超时及保存 -->
    <bean name="sessionValidationScheduler"
        class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
        <property name="interval" value="1800000" />
        <property name="sessionManager" ref="sessionManager" />
    </bean>
    <!-- 会话管理器 -->
    <bean id="sessionManager"
        class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 全局会话超时时间(单位毫秒),默认30分钟 -->
        <property name="globalSessionTimeout" value="1800000" />
        <property name="deleteInvalidSessions" value="true" />
        <property name="sessionValidationSchedulerEnabled" value="true" />
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
        <property name="sessionDAO" ref="sessionDAO" />
        <property name="sessionIdCookieEnabled" value="true" />
        <property name="sessionIdCookie" ref="sessionIdCookie" />
    </bean>
    

    7.自定义Realm

    <bean id="databaseRealm" class="com.shiro.service.impl.DatabaseRealm">
          <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    
    • 核心代码:
      因为这是基于url的权限管理,所以,就不会再有ini配置文件了(实际上配置到Spring中),Realm直接从数据库中获取用户的信息,给用户做验证和授权操作。
      最核心的代码还是自定义Realm:
    public class DatabaseRealm extends AuthorizingRealm {
    
      @Autowired
      private UserService userService;
      @Autowired
      private RoleService roleService;
      @Autowired
      private PermissionService permissionService;
    
      @Override
      protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户名
        String userName = (String) principalCollection.getPrimaryPrincipal();
        //从数据库中获取角色和权限
        Set<String> permissions = permissionService.getStringPermissionByName(userName);
        Set<String> roles = roleService.listPermissionURLs(userName);
    
        //建立简单授权对象
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        //设置权限和角色
        s.setStringPermissions(permissions);
        s.setRoles(roles);
        //授权
        return s;
      }
    
      @Override
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String userName = token.getPrincipal().toString();
        User user = userService.getUser(userName);
        String passwordInDB = user.getPassword();
        System.out.println(passwordInDB);
        System.out.println(t.getPassword());
        String salt = user.getSalt();
        //做用户验证
        SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt),
            getName());
        return a;
      }
    }
    

    Subject会将用户的信息交给上述Realm做验证和授权,做验证很好理解,登录即验证,但是要怎么判断一个url是否要做权限验证呢?
    在PermissionService中有两个方法,分别是needIntercepterlistPermissionURLs,第一个方法的作用是判断一个url是否要验证,如果权限表中有这个url,则需要进行授权,如果没有则直接放行,listPermissionURLs是判断一个用户有权访问的所有url

    @Override
      public boolean needInterceptor(String requestURI) {
          // TODO Auto-generated method stub
          List<Permission> permissionList = permissionDao.listPermission();
          for(Permission p : permissionList) {
              if(p.getUrl().equals(requestURI)) {
                  return true;
              }
          }
          return false;
      }
    
      @Override
      public Set<String> listPermissionURLs(String userName) {
          // TODO Auto-generated method stub
          Set<String> result = new HashSet<>();
          List<Permission> permissions = permissionDao.queryPermissionByUsername(userName);
          for(Permission p : permissions) {
              result.add(p.getUrl());
          }
          return result;
      }
    

    于是,在过滤器和Realm中就可以调用这两个方法判断是否需要授权和进行授权了。
    过滤器类:

    public class URLPathMatchingFilter extends PathMatchingFilter {
      @Autowired
      PermissionService permissionService;
    
      @Override
      protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)
          throws Exception {
        String requestURI = getPathWithinApplication(request);
    
        System.out.println("requestURI:" + requestURI);
    
        Subject subject = SecurityUtils.getSubject();
        // 如果没有登录,就跳转到登录页面
        if (!subject.isAuthenticated()) {
          WebUtils.issueRedirect(request, response, "/login");
          return false;
        }
    
        // 看看这个路径权限里有没有维护,如果没有维护,一律放行(也可以改为一律不放行)
        boolean needInterceptor = permissionService.needInterceptor(requestURI);
        if (!needInterceptor) {
          return true;
        } else {
          //如果有维护判断是否有权限访问(进入Realm进行授权)
          if (subject.isPermitted(requestURI))
            return true;
          else {
            UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径 " + requestURI + " 的权限");
    
            subject.getSession().setAttribute("ex", ex);
    
            WebUtils.issueRedirect(request, response, "/unauthorized");
            return false;
          }
        }
      }
    }
    
  • 最后运行结果

在运行最后结果之前,先明确一下数据表中用户的角色与权限。

权限关系

所以运行结果如下:

  • 登录Object

    登录Object
  • Object登录成功


    Object登录成功
  • Object访问addBlog


    Object访问增加博客
  • Object访问readBlog


    Object访问阅读博客
  • 登录Reader


    登录Reader
  • Reader访问addBlog


    Reader访问增加博客
  • Reader访问readBlog


    Reader访问阅读博客

结语

两天时间学完Shiro并写完了自己的学习笔记,从网上找资料学习到遇到各种坑,到差点偏离Shiro的方向,好在还是完成了Shiro的修炼,总算理解了这一系列验证及授权的流程,之后应该将Shiro结合项目使用,并去理解其原理和工作流程。

参考资料:Shiro系列教材

欢迎大家访问我的个人博客:Object's Blog

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

推荐阅读更多精彩内容