微服務(wù)權(quán)限終極解決方案,Spring Cloud Gateway + Oauth2 實(shí)現(xiàn)統(tǒng)一認(rèn)證和鑒權(quán)饶套!

最近發(fā)現(xiàn)了一個(gè)很好的微服務(wù)權(quán)限解決方案漩蟆,可以通過認(rèn)證服務(wù)進(jìn)行統(tǒng)一認(rèn)證,然后通過網(wǎng)關(guān)來統(tǒng)一校驗(yàn)認(rèn)證和鑒權(quán)妓蛮。此方案為目前最新方案怠李,僅支持Spring Boot 2.2.0、Spring Cloud Hoxton 以上版本仔引,本文將詳細(xì)介紹該方案的實(shí)現(xiàn),希望對(duì)大家有所幫助褐奥!

SpringBoot實(shí)戰(zhàn)電商項(xiàng)目mall(35k+star)地址:https://github.com/macrozheng/mall

前置知識(shí)

我們將采用Nacos作為注冊(cè)中心咖耘,Gateway作為網(wǎng)關(guān),使用nimbus-jose-jwtJWT庫操作JWT令牌撬码,對(duì)這些技術(shù)不了解的朋友可以看下下面的文章儿倒。

應(yīng)用架構(gòu)

我們理想的解決方案應(yīng)該是這樣的夫否,認(rèn)證服務(wù)負(fù)責(zé)認(rèn)證,網(wǎng)關(guān)負(fù)責(zé)校驗(yàn)認(rèn)證和鑒權(quán)叫胁,其他API服務(wù)負(fù)責(zé)處理自己的業(yè)務(wù)邏輯凰慈。安全相關(guān)的邏輯只存在于認(rèn)證服務(wù)和網(wǎng)關(guān)服務(wù)中,其他服務(wù)只是單純地提供服務(wù)而沒有任何安全相關(guān)邏輯驼鹅。

相關(guān)服務(wù)劃分:

  • micro-oauth2-gateway:網(wǎng)關(guān)服務(wù)微谓,負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)和鑒權(quán)功能,整合Spring Security+Oauth2输钩;
  • micro-oauth2-auth:Oauth2認(rèn)證服務(wù)豺型,負(fù)責(zé)對(duì)登錄用戶進(jìn)行認(rèn)證,整合Spring Security+Oauth2买乃;
  • micro-oauth2-api:受保護(hù)的API服務(wù)姻氨,用戶鑒權(quán)通過后可以訪問該服務(wù),不整合Spring Security+Oauth2剪验。

方案實(shí)現(xiàn)

下面介紹下這套解決方案的具體實(shí)現(xiàn)肴焊,依次搭建認(rèn)證服務(wù)前联、網(wǎng)關(guān)服務(wù)和API服務(wù)。

micro-oauth2-auth

我們首先來搭建認(rèn)證服務(wù)抖韩,它將作為Oauth2的認(rèn)證服務(wù)使用蛀恩,并且網(wǎng)關(guān)服務(wù)的鑒權(quán)功能也需要依賴它。

  • pom.xml中添加相關(guān)依賴茂浮,主要是Spring Security双谆、Oauth2、JWT席揽、Redis相關(guān)依賴顽馋;
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>8.16</version>
    </dependency>
    <!-- redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

  • application.yml中添加相關(guān)配置,主要是Nacos和Redis相關(guān)配置幌羞;
server:
  port: 9401
spring:
  profiles:
    active: dev
  application:
    name: micro-oauth2-auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  redis:
    database: 0
    port: 6379
    host: localhost
    password: 
management:
  endpoints:
    web:
      exposure:
        include: "*"

  • 使用keytool生成RSA證書jwt.jks寸谜,復(fù)制到resource目錄下,在JDK的bin目錄下使用如下命令即可属桦;
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks

  • 創(chuàng)建UserServiceImpl類實(shí)現(xiàn)Spring Security的UserDetailsService接口熊痴,用于加載用戶信息;
/**
 * 用戶管理業(yè)務(wù)類
 * Created by macro on 2020/6/19.
 */
@Service
public class UserServiceImpl implements UserDetailsService {

    private List<UserDTO> userList;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostConstruct
    public void initData() {
        String password = passwordEncoder.encode("123456");
        userList = new ArrayList<>();
        userList.add(new UserDTO(1L,"macro", password,1, CollUtil.toList("ADMIN")));
        userList.add(new UserDTO(2L,"andy", password,1, CollUtil.toList("TEST")));
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<UserDTO> findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
        if (CollUtil.isEmpty(findUserList)) {
            throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
        }
        SecurityUser securityUser = new SecurityUser(findUserList.get(0));
        if (!securityUser.isEnabled()) {
            throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
        } else if (!securityUser.isAccountNonLocked()) {
            throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
        } else if (!securityUser.isAccountNonExpired()) {
            throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
        } else if (!securityUser.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
        }
        return securityUser;
    }

}

  • 添加認(rèn)證服務(wù)相關(guān)配置Oauth2ServerConfig聂宾,需要配置加載用戶信息的服務(wù)UserServiceImpl及RSA的鑰匙對(duì)KeyPair果善;
/**
 * 認(rèn)證服務(wù)器配置
 * Created by macro on 2020/6/19.
 */
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    private final PasswordEncoder passwordEncoder;
    private final UserServiceImpl userDetailsService;
    private final AuthenticationManager authenticationManager;
    private final JwtTokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-app")
                .secret(passwordEncoder.encode("123456"))
                .scopes("all")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(86400);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer); 
        delegates.add(accessTokenConverter());
        enhancerChain.setTokenEnhancers(delegates); //配置JWT的內(nèi)容增強(qiáng)器
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService) //配置加載用戶信息的服務(wù)
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(enhancerChain);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }

    @Bean
    public KeyPair keyPair() {
        //從classpath下的證書中獲取秘鑰對(duì)
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }

}

  • 如果你想往JWT中添加自定義信息的話,比如說登錄用戶的ID系谐,可以自己實(shí)現(xiàn)TokenEnhancer接口巾陕;
/**
 * JWT內(nèi)容增強(qiáng)器
 * Created by macro on 2020/6/19.
 */
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        Map<String, Object> info = new HashMap<>();
        //把用戶ID設(shè)置到JWT中
        info.put("id", securityUser.getId());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}

  • 由于我們的網(wǎng)關(guān)服務(wù)需要RSA的公鑰來驗(yàn)證簽名是否合法,所以認(rèn)證服務(wù)需要有個(gè)接口把公鑰暴露出來纪他;
/**
 * 獲取RSA公鑰接口
 * Created by macro on 2020/6/19.
 */
@RestController
public class KeyPairController {

    @Autowired
    private KeyPair keyPair;

    @GetMapping("/rsa/publicKey")
    public Map<String, Object> getKey() {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAKey key = new RSAKey.Builder(publicKey).build();
        return new JWKSet(key).toJSONObject();
    }

}

  • 不要忘了還需要配置Spring Security鄙煤,允許獲取公鑰接口的訪問;
/**
 * SpringSecurity配置
 * Created by macro on 2020/6/19.
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                .antMatchers("/rsa/publicKey").permitAll()
                .anyRequest().authenticated();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

  • 創(chuàng)建一個(gè)資源服務(wù)ResourceServiceImpl茶袒,初始化的時(shí)候把資源與角色匹配關(guān)系緩存到Redis中梯刚,方便網(wǎng)關(guān)服務(wù)進(jìn)行鑒權(quán)的時(shí)候獲取。
/**
 * 資源與角色匹配關(guān)系管理業(yè)務(wù)類
 * Created by macro on 2020/6/19.
 */
@Service
public class ResourceServiceImpl {

    private Map<String, List<String>> resourceRolesMap;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @PostConstruct
    public void initData() {
        resourceRolesMap = new TreeMap<>();
        resourceRolesMap.put("/api/hello", CollUtil.toList("ADMIN"));
        resourceRolesMap.put("/api/user/currentUser", CollUtil.toList("ADMIN", "TEST"));
        redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ROLES_MAP, resourceRolesMap);
    }
}

micro-oauth2-gateway

接下來我們就可以搭建網(wǎng)關(guān)服務(wù)了薪寓,它將作為Oauth2的資源服務(wù)乾巧、客戶端服務(wù)使用,對(duì)訪問微服務(wù)的請(qǐng)求進(jìn)行統(tǒng)一的校驗(yàn)認(rèn)證和鑒權(quán)操作预愤。

  • pom.xml中添加相關(guān)依賴沟于,主要是Gateway、Oauth2和JWT相關(guān)依賴植康;
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-jose</artifactId>
    </dependency>
    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>8.16</version>
    </dependency>
</dependencies>

  • application.yml中添加相關(guān)配置旷太,主要是路由規(guī)則的配置、Oauth2中RSA公鑰的配置及路由白名單的配置;
server:
  port: 9201
spring:
  profiles:
    active: dev
  application:
    name: micro-oauth2-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes: #配置路由規(guī)則
        - id: oauth2-api-route
          uri: lb://micro-oauth2-api
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1
        - id: oauth2-auth-route
          uri: lb://micro-oauth2-auth
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
      discovery:
        locator:
          enabled: true #開啟從注冊(cè)中心動(dòng)態(tài)創(chuàng)建路由的功能
          lower-case-service-id: true #使用小寫服務(wù)名供璧,默認(rèn)是大寫
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: 'http://localhost:9401/rsa/publicKey' #配置RSA的公鑰訪問地址
  redis:
    database: 0
    port: 6379
    host: localhost
    password: 
secure:
  ignore:
    urls: #配置白名單路徑
      - "/actuator/**"
      - "/auth/oauth/token"

  • 對(duì)網(wǎng)關(guān)服務(wù)進(jìn)行配置安全配置存崖,由于Gateway使用的是WebFlux,所以需要使用@EnableWebFluxSecurity注解開啟睡毒;
/**
 * 資源服務(wù)器配置
 * Created by macro on 2020/6/19.
 */
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
    private final AuthorizationManager authorizationManager;
    private final IgnoreUrlsConfig ignoreUrlsConfig;
    private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
        http.authorizeExchange()
                .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名單配置
                .anyExchange().access(authorizationManager)//鑒權(quán)管理器配置
                .and().exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)//處理未授權(quán)
                .authenticationEntryPoint(restAuthenticationEntryPoint)//處理未認(rèn)證
                .and().csrf().disable();
        return http.build();
    }

    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }

}

  • WebFluxSecurity中自定義鑒權(quán)操作需要實(shí)現(xiàn)ReactiveAuthorizationManager接口来惧;
/**
 * 鑒權(quán)管理器,用于判斷是否有資源的訪問權(quán)限
 * Created by macro on 2020/6/19.
 */
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        //從Redis中獲取當(dāng)前路徑可訪問角色列表
        URI uri = authorizationContext.getExchange().getRequest().getURI();
        Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());
        List<String> authorities = Convert.toList(String.class,obj);
        authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
        //認(rèn)證通過且角色匹配的用戶可訪問當(dāng)前路徑
        return mono
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(authorities::contains)
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

}

  • 這里我們還需要實(shí)現(xiàn)一個(gè)全局過濾器AuthGlobalFilter演顾,當(dāng)鑒權(quán)通過后將JWT令牌中的用戶信息解析出來供搀,然后存入請(qǐng)求的Header中,這樣后續(xù)服務(wù)就不需要解析JWT令牌了钠至,可以直接從請(qǐng)求的Header中獲取到用戶信息葛虐。
/**
 * 將登錄用戶的JWT轉(zhuǎn)化成用戶信息的全局過濾器
 * Created by macro on 2020/6/17.
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StrUtil.isEmpty(token)) {
            return chain.filter(exchange);
        }
        try {
            //從token中解析用戶信息并設(shè)置到Header中去
            String realToken = token.replace("Bearer ", "");
            JWSObject jwsObject = JWSObject.parse(realToken);
            String userStr = jwsObject.getPayload().toString();
            LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);
            ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
            exchange = exchange.mutate().request(request).build();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

micro-oauth2-api

最后我們搭建一個(gè)API服務(wù),它不會(huì)集成和實(shí)現(xiàn)任何安全相關(guān)邏輯棉钧,全靠網(wǎng)關(guān)來保護(hù)它屿脐。

  • pom.xml中添加相關(guān)依賴,就添加了一個(gè)web依賴宪卿;
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

  • application.yml添加相關(guān)配置的诵,很常規(guī)的配置;
server:
  port: 9501
spring:
  profiles:
    active: dev
  application:
    name: micro-oauth2-api
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
management:
  endpoints:
    web:
      exposure:
        include: "*"

  • 創(chuàng)建一個(gè)測(cè)試接口佑钾,網(wǎng)關(guān)驗(yàn)證通過即可訪問西疤;
/**
 * 測(cè)試接口
 * Created by macro on 2020/6/19.
 */
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello World.";
    }

}

  • 創(chuàng)建一個(gè)LoginUserHolder組件,用于從請(qǐng)求的Header中直接獲取登錄用戶信息次绘;
/**
 * 獲取登錄用戶信息
 * Created by macro on 2020/6/17.
 */
@Component
public class LoginUserHolder {

    public UserDTO getCurrentUser(){
        //從Header中獲取用戶信息
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String userStr = request.getHeader("user");
        JSONObject userJsonObject = new JSONObject(userStr);
        UserDTO userDTO = new UserDTO();
        userDTO.setUsername(userJsonObject.getStr("user_name"));
        userDTO.setId(Convert.toLong(userJsonObject.get("id")));
        userDTO.setRoles(Convert.toList(String.class,userJsonObject.get("authorities")));
        return userDTO;
    }
}

  • 創(chuàng)建一個(gè)獲取當(dāng)前用戶信息的接口瘪阁。
/**
 * 獲取登錄用戶信息接口
 * Created by macro on 2020/6/19.
 */
@RestController
@RequestMapping("/user")
public class UserController{

    @Autowired
    private LoginUserHolder loginUserHolder;

    @GetMapping("/currentUser")
    public UserDTO currentUser() {
        return loginUserHolder.getCurrentUser();
    }

}

功能演示

接下來我們來演示下微服務(wù)系統(tǒng)中的統(tǒng)一認(rèn)證鑒權(quán)功能撒遣,所有請(qǐng)求均通過網(wǎng)關(guān)訪問邮偎。

  • 在此之前先啟動(dòng)我們的Nacos和Redis服務(wù),然后依次啟動(dòng)micro-oauth2-auth义黎、micro-oauth2-gatewaymicro-oauth2-api服務(wù)禾进;
image
image
image
image
image
  • 使用沒有訪問權(quán)限的andy賬號(hào)登錄宠纯,訪問接口時(shí)會(huì)返回如下信息,訪問地址:http://localhost:9201/api/hello
image

項(xiàng)目源碼地址

https://github.com/macrozheng/springcloud-learning/tree/master/micro-oauth2

本文 GitHub https://github.com/macrozheng/mall-learning 已經(jīng)收錄层释,歡迎大家Star婆瓜!

作者:夢(mèng)想de星空
鏈接:http://www.reibang.com/p/1e974fc91f74

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子廉白,更是在濱河造成了極大的恐慌个初,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猴蹂,死亡現(xiàn)場(chǎng)離奇詭異院溺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)磅轻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門珍逸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓢省,你說我怎么就攤上這事弄息。” “怎么了勤婚?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵摹量,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我馒胆,道長(zhǎng)缨称,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任祝迂,我火速辦了婚禮睦尽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘型雳。我一直安慰自己当凡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布纠俭。 她就那樣靜靜地躺著沿量,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冤荆。 梳的紋絲不亂的頭發(fā)上朴则,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音钓简,去河邊找鬼乌妒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛外邓,可吹牛的內(nèi)容都是我干的撤蚊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼损话,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼侦啸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤匹中,失蹤者是張志新(化名)和其女友劉穎夏漱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顶捷,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挂绰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了服赎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葵蒂。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖重虑,靈堂內(nèi)的尸體忽然破棺而出践付,到底是詐尸還是另有隱情,我是刑警寧澤缺厉,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布永高,位于F島的核電站,受9級(jí)特大地震影響提针,放射性物質(zhì)發(fā)生泄漏命爬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一辐脖、第九天 我趴在偏房一處隱蔽的房頂上張望饲宛。 院中可真熱鬧,春花似錦嗜价、人聲如沸艇抠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽家淤。三九已至,卻和暖如春奴拦,著一層夾襖步出監(jiān)牢的瞬間媒鼓,已是汗流浹背届吁。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工错妖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疚沐。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓暂氯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親亮蛔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痴施,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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