SpringBoot集成Shiro安全框架

Shiro主要用来进行权限管理。简单的介绍如下:

一、概念

Shiro是一个安全框架,可以进行角色、权限管理。

Shiro主要功能如下:

Authentication身份认证/登录,验证用户是不是拥有相应的身份;

Authorization授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web SupportWeb支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrencyshiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing提供测试支持;

Run As允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

二、主要的类

1.Subject:当前用户,Subject可以是一个人,也可以是第三方服务

2.SecurityManager:管理所有Subject,可以配合内部安全组件。

3.principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。

4.credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码了。

5.Realms:用于进行权限信息的验证,需要自己实现。

6.Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。

在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。

7.SimpleHash,可以通过特定算法(比如md5)配合盐值salt,对密码进行多次加密。

首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:


可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

Subject主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。


       接下来我们来从Shiro内部来看下Shiro的架构,如下图所示:


Subject主体,可以看到主体可以是任何可以与应用交互的“用户”;

SecurityManager相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

SessionManager如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

SessionDAODAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

三、Shiro配置

1.Spring集成Shiro一般通过xml配置,SpringBoot集成Shiro一般通过java代码配合@Configuration和@Bean配置。

2.Shiro的核心通过过滤器Filter实现。Shiro中的Filter是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。

3.SpringBoot集成Shiro,我们需要写的主要是两个类,ShiroConfiguration类,还有继承了AuthorizingRealm的Realm类

ShiroConfiguration类,用来配置Shiro,注入各种Bean。

包括过滤器(shiroFilter)、安全事务管理器(SecurityManager)、密码凭证(CredentialsMatcher)、aop注解支持(authorizationAttributeSourceAdvisor)等等

Realm类,包括登陆认证(doGetAuthenticationInfo)、授权认证(doGetAuthorizationInfo)

四、集成如下:

1.依赖

<dependency>

           <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-spring</artifactId>

          <version>1.2.3</version>

</dependency>

2.实体类

实体有三个,根据规则会自动生成两个中间表,数据库实际上会生成5张表。

分别是User,SysRole,SysPermission,中间表按照@JoinTable来生成。

@Entitypublicclass User {

    @Id

    @GenericGenerator(name="generator",strategy = "native")

    @GeneratedValue(generator = "generator")

    private Integer userId;

    @Column(nullable =false, unique =true)

    privateString userName;//登录用户名@Column(nullable =false)

    privateString name;//名称(昵称或者真实姓名,根据实际情况定义)@Column(nullable =false)

    private String password;

    privateString salt;//加密密码的盐privatebytestate;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常        状态,2:用户被锁定.

   @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;@JoinTable(name = "SysUserRole", joinColumns = {

   @JoinColumn(name = "userId") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })

    privateList roleList;// 一个用户具有多个角色@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")

    privateLocalDateTime createTime;//创建时间@DateTimeFormat(pattern = "yyyy-MM-dd")

    privateLocalDate expiredDate;//过期日期private String email;

    public String getEmail() {

        return email;

    }

    publicvoid setEmail(String email) {

        this.email = email;

    }

    public LocalDateTime getCreateTime() {

        return createTime;

    }

    publicvoid setCreateTime(LocalDateTime createTime) {

        this.createTime = createTime;

    }

    public LocalDate getExpiredDate() {

        return expiredDate;

    }

    publicvoid setExpiredDate(LocalDate expiredDate) {

        this.expiredDate = expiredDate;

    }

    public Integer getUserId() {

        return userId;

    }

    publicvoid setUserId(Integer userId) {

        this.userId = userId;

    }

    public String getUserName() {

        return userName;

    }

    publicvoid setUserName(String userName) {

        this.userName = userName;

    }

    public String getName() {

        return name;

    }

    publicvoid setName(String name) {

        this.name = name;

    }

    public String getPassword() {

        return password;

    }

    publicvoid setPassword(String password) {

        this.password = password;

    }

    public String getSalt() {

        return salt;

    }

    publicvoid setSalt(String salt) {

        this.salt = salt;

    }

    publicbyte getState() {

        return state;

    }

    publicvoidsetState(byte state) {

        this.state = state;

    }

    publicList getRoleList() {

        return roleList;

    }

    publicvoidsetRoleList(List roleList) {

        this.roleList = roleList;

    }

    /*** 密码盐.重新对盐重新进行了定义,用户名+salt,这样就不容易被破解,可以采用多种方式定义加盐*@return*/public String getCredentialsSalt(){

        returnthis.userName+this.salt;

    }

}

@Entitypublicclass 

   SysRole {

    @Id

    @GenericGenerator(name="generator",strategy = "native")

    @GeneratedValue(generator = "generator")

    privateInteger roleId;// 编号@Column(nullable =false, unique =true)

    privateString role;// 角色标识程序中判断使用,如"admin",这个是唯一的:privateString description;// 角色描述,UI界面显示使用privateBoolean available = Boolean.TRUE;// 是否可用,如果不可用将不会添加给用户

    //角色 -- 权限关系:多对多关系;@ManyToMany(fetch= FetchType.EAGER)

    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})

    privateList permissions;

    // 用户 - 角色关系定义;    @ManyToMany

    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")})

    privateList users;// 一个角色对应多个用户public Integer getRoleId() {

        return roleId;

    }

    publicvoid setRoleId(Integer roleId) {

        this.roleId = roleId;

    }

    public String getRole() {

        return role;

    }

    publicvoid setRole(String role) {

        this.role = role;

    }

    public String getDescription() {

        return description;

    }

    publicvoid setDescription(String description) {

        this.description = description;

    }

    public Boolean getAvailable() {

        return available;

    }

    publicvoid setAvailable(Boolean available) {

        this.available = available;

    }

    publicList getPermissions() {

        return permissions;

    }

    publicvoidsetPermissions(List permissions) {

        this.permissions = permissions;

    }

    publicList getUsers() {

        return users;

    }

    publicvoidsetUsers(List users) {

        this.users = users;

    }

}


@Entitypublicclass

   SysPermission {

    @Id

    @GenericGenerator(name="generator",strategy = "native")

    @GeneratedValue(generator = "generator")

    privateInteger permissionId;//主键.@Column(nullable =false)

    privateString permissionName;//名称.@Column(columnDefinition="enum('menu','button')")

    privateString resourceType;//资源类型,[menu|button]privateString url;//资源路径.privateString permission;//权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:viewprivateLong parentId;//父编号privateString parentIds;//父编号列表privateBoolean available = Boolean.TRUE;

    //角色 -- 权限关系:多对多关系;    @ManyToMany

    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})

    privateList roles;

    public Integer getPermissionId() {

        return permissionId;

    }

    publicvoid setPermissionId(Integer permissionId) {

        this.permissionId = permissionId;

    }

    public String getPermissionName() {

        return permissionName;

    }

    publicvoid setPermissionName(String permissionName) {

        this.permissionName = permissionName;

    }

    public String getResourceType() {

        return resourceType;

    }

    publicvoid setResourceType(String resourceType) {

        this.resourceType = resourceType;

    }

    public String getUrl() {

        return url;

    }

    publicvoid setUrl(String url) {

        this.url = url;

    }

    public String getPermission() {

        return permission;

    }

    publicvoid setPermission(String permission) {

        this.permission = permission;

    }

    public Long getParentId() {

        return parentId;

    }

    publicvoid setParentId(Long parentId) {

        this.parentId = parentId;

    }

    public String getParentIds() {

        return parentIds;

    }

    publicvoid setParentIds(String parentIds) {

        this.parentIds = parentIds;

    }

    public Boolean getAvailable() {

        return available;

    }

    publicvoid setAvailable(Boolean available) {

        this.available = available;

    }

    publicList getRoles() {

        return roles;

    }

    publicvoidsetRoles(List roles) {

        this.roles = roles;

    }

}

3.配置类

ShiroConfiguration.java如下

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;

import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.LifecycleBeanPostProcessor;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

import org.apache.shiro.web.mgt.CookieRememberMeManager;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.apache.shiro.web.servlet.Cookie;import org.apache.shiro.web.servlet.SimpleCookie;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;

import java.util.LinkedHashMap;

import java.util.Map;

@Configurationpublicclass 

public class ShiroConfiguration {

    @Bean

    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        //设置安全管理器        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //默认跳转到登陆页面shiroFilterFactoryBean.setLoginUrl("/login");

        //登陆成功后的页面shiroFilterFactoryBean.setSuccessUrl("/index");

        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        //自定义过滤器Map filterMap=newLinkedHashMap<>();

        shiroFilterFactoryBean.setFilters(filterMap);

        //权限控制mapMap filterChainDefinitionMap=newLinkedHashMap<>();

        // 配置不会被拦截的链接 顺序判断filterChainDefinitionMap.put("/static/**", "anon");

        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 

       filterChainDefinitionMap.put("/logout", "logout");

//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;////<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->

//       filterChainDefinitionMap.put("/**", "anon");        

         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;

    }

    /*** 核心的安全事务管理器

    * @return*/    

 @Bean

    public SecurityManager securityManager(){

        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager ();

        //设置realm        securityManager.setRealm( myShiroRealm(  )  );

        securityManager.setRememberMeManager(rememberMeManager());

        return securityManager;

    }

    /**    * 身份认证Realm,此处的注入不可以缺少。否则会在UserRealm中注入对象会报空指针.

    * @return*/    

@Bean

    public UserRealm myShiroRealm(  ){

        UserRealm myShiroRealm =new UserRealm();

        myShiroRealm.setCredentialsMatcher(  hashedCredentialsMatcher() );

        return myShiroRealm;

    }


/** * 哈希密码比较器。在myShiroRealm中作用参数使用

    * 登陆时会比较用户输入的密码,跟数据库密码配合盐值salt解密后是否一致。

    * @return*/  

  @Bean

  public HashedCredentialsMatcher hashedCredentialsMatcher(){

        HashedCredentialsMatcher hashedCredentialsMatcher =new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用md5算法;

        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5( md5(""));

        return hashedCredentialsMatcher;

    }

////注入缓存

// @Bean

//    public EhCacheManager ehCacheManager(){

//        System.out.println("ShiroConfiguration.getEhCacheManager()执行");

//        EhCacheManager cacheManager=new EhCacheManager();

//        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");

//          return cacheManager;

//    }

/** *  开启shiro aop注解支持.

    *  使用代理方式;所以需要开启代码支持;否则@RequiresRoles等注解无法生效

    * @param securityManager

    * @return*/    @Bean

    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){

        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =new AuthorizationAttributeSourceAdvisor();

        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);

        return authorizationAttributeSourceAdvisor;

    }

    /**    * Shiro生命周期处理器

    * @return*/    

@Bean

    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){

        returnnew LifecycleBeanPostProcessor();

    }

    /**    * 自动创建代理

    * @return*/    

@Bean

    @DependsOn({"lifecycleBeanPostProcessor"})

    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){

        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator =new DefaultAdvisorAutoProxyCreator();

        advisorAutoProxyCreator.setProxyTargetClass(true);

        return advisorAutoProxyCreator;

    }

}

Realm类如下:

package com.example.demo.config;

import com.example.demo.pojo.SysPermission;

import com.example.demo.pojo.SysUserRole;

import com.example.demo.pojo.User;

import com.example.demo.service.UserSerevice;

import com.example.demo.utils.State;

import org.apache.log4j.Logger;importorg.apache.shiro.authc.*;

import org.apache.shiro.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.realm.AuthorizingRealm;

import org.apache.shiro.subject.PrincipalCollection;

import org.apache.shiro.util.ByteSource

;import javax.annotation.Resource;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.List;import java.util.Set;

import static org.apache.coyote.http11.Constants.a;

publicclassUserRealmextends AuthorizingRealm {

    @Resource(name = "userServiceImp")

    private UserSerevice userService;

    privateLogger logger=Logger.getLogger(UserRealm.class);

    /*** 提供用户信息,返回权限信息

    * @param principals

    * @return*/    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        logger.info("---------------------------->授权认证:");

        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();

        String userName=(String) principals.getPrimaryPrincipal();

        String userId=userService.findUserIdByName(userName);

        Set roleIdSet=userService.findRoleIdByUid( Integer.parseInt(userId) );

        Set roleSet=newHashSet<>();

        Set  pemissionIdSet=newHashSet<>();

        Set  pemissionSet=newHashSet<>();

        for(SysUserRole roleInfo : roleIdSet) {

              introleId=roleInfo.getRoleId();

              roleSet.add( userService.findRoleByRoleId( roleId  ) );

              //将拥有角色的所有权限放进Set里面,也就是求Set集合的并集

              //由于我这边的数据表设计得不太好,所以提取set集合比较麻烦              

       pemissionIdSet.addAll( userService.findPermissionIdByRoleId(  roleId ))}

        for(int permissionId : pemissionIdSet) {

            String permission= userService.findPermissionById( permissionId ).getPermission() ;

            pemissionSet.add(  permission );

        }

        // 将角色名称组成的Set提供给授权info       

      authorizationInfo.setRoles( roleSet );

        // 将权限名称组成的Set提供给info        

        authorizationInfo.setStringPermissions(pemissionSet);

        return authorizationInfo;

    }

    /*** 提供帐户信息,返回认证信息

    * @param authenticationToken

    * @return*@throws AuthenticationException

    */  @Override

    protectedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {

        logger.info("---------------------------->登陆验证:");

        String userName=(String)authenticationToken.getPrincipal();

        User user=userService.findUserByName(userName);

        if(user==null) {

            //用户不存在就抛出异常

            thrownew UnknownAccountException();

        }

        if( State.LOCKED.equals( user.getState() )  ) {

          //用户被锁定就抛异常

         thrownew  LockedAccountException();

        }

        //密码可以通过SimpleHash加密,然后保存进数据库。

        //此处是获取数据库内的账号、密码、盐值,保存到登陆信息info中

SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user.getUsername(),

                user.getPassword(),

                ByteSource.Util.bytes(user.getSalt())  ,

                getName());                 

              //realm namereturn authenticationInfo;

    }

}

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

推荐阅读更多精彩内容