SpringBoot2.0 整合 Spring Security OAuth2仅颇,基于JDBC存儲(chǔ)令牌

1.jpg

概述

SpringBoot版本為 2.0.6.RELEASE蝴韭;

數(shù)據(jù)庫使用的是MySQL咏尝;

配置Spring Security OAuth2大概可以分為4各部分:

  1. 實(shí)現(xiàn)UserDetailsService;
  2. 配置安全配置(繼承WebSecurityConfigurerAdapter)
  3. 配置認(rèn)證服務(wù)器(繼承AuthorizationServerConfigurerAdapter)
  4. 配置資源服務(wù)器(繼承ResourceServerConfigurerAdapter)

本文主要講述配置以上4個(gè)方面褂傀,其實(shí)如果你是基于RBAC角色管理或者其他的忍啤,仍需要另外的配置UserDetails(USER實(shí)體類去實(shí)現(xiàn))等等。配置上有不懂的地方可以先查看注解以及代碼塊下有備注紊服,因?yàn)槭枪卷?xiàng)目最近自己搭建的安全框架檀轨,配置UserDetails和權(quán)限這種就不另外發(fā)布了。

pom.xml

添加依賴(部分包看自己情況選擇)

<dependencies>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- spring data jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--<dependency>-->
            <!--<groupId>com.h2database</groupId>-->
            <!--<artifactId>h2</artifactId>-->
            <!--<scope>runtime</scope>-->
        <!--</dependency>-->

        <!-- web socket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!-- integration -->
        <!--<dependency>-->
            <!--<groupId>org.springframework.boot</groupId>-->
            <!--<artifactId>spring-boot-starter-integration</artifactId>-->
        <!--</dependency>-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.integration</groupId>-->
            <!--<artifactId>spring-integration-mqtt</artifactId>-->
        <!--</dependency>-->

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>

        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!-- lomBok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.8</version>
            <scope>provided</scope>
        </dependency>

        <!-- poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.14</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.14</version>
        </dependency>

        <!-- http -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5</version>
        </dependency>



        <!-- devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.37</version>
        </dependency>

        <!-- 定時(shí)器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

        <!--websocket support-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-reactor-netty</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin.external.google</groupId>
            <artifactId>android-json</artifactId>
            <version>0.0.20131108.vaadin1</version>
            <scope>compile</scope>
        </dependency>

        <!--end websocket-->

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math</artifactId>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>


    </dependencies>

實(shí)現(xiàn)UserDetailsService

/**
 * @author lewis
 * @date 2019/12/23 14:24
 */
public class UserDetailsServiceImpl implements UserDetailsService {

    private final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        List<User> userList = userRepository.findByLoginNo(s);
        if (userList != null && userList.size() != 0){
            User user = userList.get(0);

            logger.info("賬號(hào)loginNo:" + user.getLoginNo() +"," + user.getPassword());

            Collection<SimpleGrantedAuthority> collection =  new HashSet<>();
            List<Role> roles = roleRepository.findByUser(user.getId());
            if (roles != null && roles.size() != 0){
                Iterator<Role> iterator = roles.iterator();
                while (iterator.hasNext()){
                    collection.add(new SimpleGrantedAuthority(iterator.next().getName()));
                }
            }
            return new org.springframework.security.core.userdetails.User(user.getLoginNo(),user.getPassword(),user.isEnabled(),
                    user.isAccountNonExpired(),user.isCredentialsNonExpired(),
                    user.isAccountNonLocked(),collection);
        }
        throw new UsernameNotFoundException("沒有這個(gè)用戶");
    }
}

備注:user實(shí)體類要先實(shí)現(xiàn)UserDetails欺嗤,看自己情況配置参萄。但是user.isEnabled(),user.isAccountNonExpired(),user.isCredentialsNonExpired(),user.isAccountNonLocked()這幾項(xiàng)屬性一般都為true。

配置安全配置(繼承WebSecurityConfigurerAdapter)

/**
 * @author lewis
 * @date 2019/12/23 14:24
 */
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Bean
    @Override
    public UserDetailsService userDetailsService(){
        return new UserDetailsServiceImpl();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 加密方式
        return new BCryptPasswordEncoder();
    }

    @Autowired
    protected void globalUserDetails(AuthenticationManagerBuilder auth)throws Exception{
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    /*
    spring security 先通過ResourceSecurityConfiguration的配置先執(zhí)行
    煎饼,再執(zhí)行webSecurityConfiguration的配置讹挎,所以要規(guī)劃好不同斷點(diǎn)對(duì)應(yīng)的過濾鏈不沖突,需要清晰規(guī)劃好吆玖。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        logger.info("web_security");
//  配置開放與限制的斷點(diǎn)有順序之分筒溃,順序錯(cuò)了,某些版本可能用不了
        http
                .antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/oauth/**",
                        "/v1.1/**",
                        "/v1.0/**",
                        "/open/**") //
                .permitAll()//  在webSecurityConfiguration都不需要web的認(rèn)證
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()//除了上面的沾乘,任何請(qǐng)求都要認(rèn)證
                .and()
                .csrf().requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize")).disable()
                .sessionManagement().maximumSessions(1);//  最大會(huì)話數(shù)設(shè)置

    }

    /**
     * 給認(rèn)證服務(wù)器使用
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

配置認(rèn)證服務(wù)器(繼承AuthorizationServerConfigurerAdapter)

/**
 * @author lewis
 * @date 2019/12/23 14:24
 */
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{

    @Resource(description = "dataSource")
    private DataSource dataSource;

    /*
    管理access_token的發(fā)放
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        // 存數(shù)據(jù)庫
        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager()).approvalStore(approvalStore())
                .userDetailsService(userDetailsService())
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) ;

        // 配置tokenServices參數(shù)
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);//可以使用refresh_token
        tokenServices.setReuseRefreshToken(false);// 刷新token后怜奖,不復(fù)用refresh_token
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds(accessTokenValiditySecond); // 30天
        tokenServices.setRefreshTokenValiditySeconds(refreshTokenValiditySecond);
        endpoints.tokenServices(tokenServices);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public TokenStore tokenStore() {
        //token保存在內(nèi)存中(也可以保存在數(shù)據(jù)庫、Redis中)翅阵。
        //如果保存在中間件(數(shù)據(jù)庫歪玲、Redis),那么資源服務(wù)器與認(rèn)證服務(wù)器可以不在同一個(gè)工程中掷匠。
        //注意:如果不保存access_token滥崩,則沒法通過access_token取得用戶信息
//      return new InMemoryTokenStore();
        return new JdbcTokenStore(dataSource); /// 使用Jdbctoken store
    }

    @Bean // 聲明 ClientDetails實(shí)現(xiàn)
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Bean
    public ApprovalStore approvalStore() throws Exception {
        TokenApprovalStore store = new TokenApprovalStore();
        store.setTokenStore(tokenStore());
        return store;
    }

    // 設(shè)置添加用戶信息,正常應(yīng)該從數(shù)據(jù)庫中讀取
    @Bean
    UserDetailsService userDetailsService() {

        return new UserDetailsServiceImpl();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        // 加密方式
        return new BCryptPasswordEncoder();
    }


    @Bean
    AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = new AuthenticationManager() {
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                return daoAuhthenticationProvider().authenticate(authentication);
            }
        };
        return authenticationManager;
    }
    @Bean
    public AuthenticationProvider daoAuhthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setTokenStore(tokenStore());
        return tokenServices;
    }
}

備注:配置完認(rèn)證服務(wù)器,需要在數(shù)據(jù)庫創(chuàng)建幾個(gè)表讹语,如下:

CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` timestamp NULL DEFAULT NULL,
  `lastModifiedAt` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

創(chuàng)建完后钙皮,仍需要在oauth_client_details表插入上面代碼在認(rèn)證服務(wù)器上的客戶端配置相應(yīng)的信息。

--按照上面客戶端配置的相應(yīng)的插入語句
INSERT INTO `navigation_server`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('kangCloud', NULL, '$2a$10$GBiczT2GQgEY.VTY1oUXT.Ey6MSvJpU2UOBe2hRFnu.G6xWqvNzuC', 'all', 'password,authorization_code,refresh_token,implicit,client_credentials', NULL, NULL, NULL, NULL, NULL, 'true');

其中因?yàn)槲以O(shè)定了客戶端密碼是密文顽决,相應(yīng)表上的client_secret也是密文短条;

還可以在密文上添加{bcrypt}的標(biāo)識(shí),告訴服務(wù)器這是哪一種加密方式才菠,例如:

{bcrypt}$2a$10$GBiczT2GQgEY.VTY1oUXT.Ey6MSvJpU2UOBe2hRFnu.G6xWqvNzuC

配置資源服務(wù)器(繼承ResourceServerConfigurerAdapter)

@Configuration
@EnableResourceServer
public class ResServerConfig extends ResourceServerConfigurerAdapter{

    private final Logger logger = LoggerFactory.getLogger(ResServerConfig.class);

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .tokenStore(tokenStore)
                .resourceId("resource_id")//    resourceId要跟認(rèn)證服務(wù)器客戶端配置的resourceId一致
                .stateless(true);
    }

    /*
    spring security 先通過ResourceSecurityConfiguration的配置先執(zhí)行
    慌烧,再執(zhí)行webSecurityConfiguration的配置,所以要規(guī)劃好不同斷點(diǎn)對(duì)應(yīng)的過濾鏈不沖突鸠儿,需要清晰規(guī)劃好屹蚊。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {

        logger.info("=======================================ResServerConfig=================================================");

        http
            .authorizeRequests()
                .antMatchers("/v1.1/**",
                        "/v1.0/**").authenticated()//   在resource配置需要認(rèn)證
                .antMatchers("/oauth/**",
                        "/open/**").permitAll() // 不需要認(rèn)證
            .and()
            .csrf().disable();

    }
}

備注:

1. 調(diào)整過濾器鏈可以在配置頭部加上@Order(int順序)1->2->3

2. 即使在application設(shè)置了server.servlet.context-path,在以上的配置類上也無需補(bǔ)上context-path

最后可以打開postman來調(diào)用是否搭建成功!

地址:http://localhost/api/oauth/token?grant_type=password&username=xxx&password=xxx&client_id=client&client_secret=secret (密碼模式)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末进每,一起剝皮案震驚了整個(gè)濱河市汹粤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌田晚,老刑警劉巖嘱兼,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贤徒,居然都是意外死亡芹壕,警方通過查閱死者的電腦和手機(jī)汇四,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踢涌,“玉大人通孽,你說我怎么就攤上這事≌霰冢” “怎么了背苦?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長潘明。 經(jīng)常有香客問我行剂,道長,這世上最難降的妖魔是什么钳降? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任厚宰,我火速辦了婚禮,結(jié)果婚禮上遂填,老公的妹妹穿的比我還像新娘固阁。我一直安慰自己,他們只是感情好城菊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布备燃。 她就那樣靜靜地躺著,像睡著了一般凌唬。 火紅的嫁衣襯著肌膚如雪并齐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天客税,我揣著相機(jī)與錄音况褪,去河邊找鬼。 笑死更耻,一個(gè)胖子當(dāng)著我的面吹牛测垛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秧均,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼食侮,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了目胡?” 一聲冷哼從身側(cè)響起锯七,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎誉己,沒想到半個(gè)月后眉尸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年噪猾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霉祸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡袱蜡,死狀恐怖丝蹭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戒劫,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布婆廊,位于F島的核電站迅细,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淘邻。R本人自食惡果不足惜茵典,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宾舅。 院中可真熱鬧统阿,春花似錦、人聲如沸筹我。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔬蕊。三九已至结澄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岸夯,已是汗流浹背麻献。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猜扮,地道東北人勉吻。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像旅赢,于是被迫代替她去往敵國和親齿桃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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