spring boot 集成jwt,security進行安全控制

spring boot 集成jwt security

文章結(jié)構(gòu)

  • 開門見山

  • 數(shù)據(jù)流程

開門見山

這一部分直接展示代碼,及將哪些代碼進行修改就可以直接移值到自己的項目進行安全驗證

代碼目錄結(jié)構(gòu)

Auth

AuthController

LoginUser

JWT

JwtUtil

Security

AuthFilter

SecurityConfig

UserDetailsImpl

UserDetailsServiceImpl

User

UserController

UserService

User

UserRepository

AuthController

說明:該類是自定義的用戶進行登錄驗證獲取token 的接口,是所有人都能訪問的

package demo.demo1.auth.api.rest;

import demo.demo1.User.model.User;
import demo.demo1.User.service.UserService;
import demo.demo1.auth.jwt.JwtUtil;
import demo.demo1.auth.model.LoginUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/auth")
@CrossOrigin(origins = "*")
@Api(
        value = "/auth",
        description = "用戶登錄認證"
)
public class AuthController {

    @Autowired
    private UserService userService;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private AuthenticationManager authenticationManager;

    @RequestMapping(value = "", method = RequestMethod.POST)
    @ApiOperation(
            value = "登錄",
            produces = "application/json"
    )
    public void login(
            @ApiParam(value = "登錄用戶名/密碼", name = "LoginUser", required = true)
            @Validated
            @RequestBody LoginUser loginUser,
            HttpServletResponse response) throws Exception {

        try {
            /** 通過security驗證登錄賬號是否正確,這里直接將用戶和密碼傳入security就好,
             * 不需要在這里進行驗證,你的驗證會在userDetailService中由security幫你進行
             */
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            loginUser.getUsername(),
                            loginUser.getPassword()
                    )
            );
        } catch (AuthenticationException e) {
            throw new Exception("Username or Password error.");
        }

        // 驗證通過后返回一個token值在http head中
        User user = userService.getUserByUserName(loginUser.getUsername());
        String token = jwtUtil.generateToken(user);
        // set token to header
        response.setHeader(JwtUtil.HEADER_STRING, token);
    }
}
LoginUser

說明: 該類是你進行登錄時的bean,這里主要就是為了和user進行區(qū)分,登錄的時候用這個bean

package demo.demo1.auth.model;

import io.swagger.annotations.ApiModel;

import javax.validation.constraints.NotNull;

@ApiModel(value = "Login User", description = "登錄用戶信息")
public class LoginUser {

    @NotNull
    private String username;

    @NotNull
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
JwtUtil

說明:這個類就是依據(jù)你的登錄用戶生成jwt token,驗證/解析請求時的token

package demo.demo1.auth.jwt;

import demo.demo1.User.model.User;
import demo.demo1.User.service.UserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
public class JwtUtil {

    /**
    token前綴
     */
    public static final String TOKEN_PREFIX = "Bearer ";

    /**
     * 設(shè)置http head中Authorization字段為token
     */
    public static final String HEADER_STRING = "Authorization";

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Autowired
    private UserService userService;

    /**
     * 依據(jù)登錄的賬號生成token
     */
    public String generateToken(User user) throws Exception {

        if (user == null || user.getId() == null) {
            throw new Exception(String.format("user %s not valid", user));
        }

        //設(shè)置token參數(shù)
        Map<String, Object> claims = new HashMap<>();
        claims.put("sub", user.getId());
        claims.put("aud", "web");
        claims.put("iss", "demo");
        claims.put("iat", new Date());

        return JwtUtil.TOKEN_PREFIX + Jwts.builder()
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();

    }

    /**
     * 解析token
     */
    public Claims parseTokenClaims(String token) throws Exception {

        try {
            String pure = token.replace(JwtUtil.TOKEN_PREFIX, "");
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(pure).getBody();
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

    }

    /**
     * 驗證token
     */
    public Boolean validateToken(String token) {
        
        try {
            String pure = token.replace(JwtUtil.TOKEN_PREFIX, "");
            Claims claims = parseTokenClaims(pure);
            String subject = claims.getSubject();
            User user = userService.getUserById(UUID.fromString(subject));
            if (user == null) {
                return false;
            } else if (claims.getExpiration().after(new Date())) {
                return true;
            }
            return false;
        } catch (Exception e) {

        }
        return false;
    }
    
}
AuthFilter

說明

這個類繼承了OncePerRequestFilter

當(dāng)發(fā)送一個攜帶token的http請求訪問某個接口的時候,這個過濾器就進行驗證其用戶權(quán)限

該類重寫了doFilterInternal方法,在該方法中通過token進行權(quán)限驗證

package demo.demo1.auth.security;

import demo.demo1.User.service.UserService;
import demo.demo1.auth.jwt.JwtUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

@Component
public class AuthFilter extends OncePerRequestFilter {

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

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private UserService userService;

    @Autowired
    private Environment env;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        String token = request.getHeader(JwtUtil.HEADER_STRING);
        if (token != null && token.startsWith(JwtUtil.TOKEN_PREFIX)) {
            token = token.replace(JwtUtil.TOKEN_PREFIX, "");
            try {
                String id = jwtUtil.parseTokenClaims(token).getSubject();
                String username = userService.getUserById(UUID.fromString(id)).getUsername();
                if (null != id && SecurityContextHolder.getContext().getAuthentication() == null) {
                    logger.debug("Checking token for user {}", id);
                    // In security, use uuid as username.
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    if (jwtUtil.validateToken(token) && userDetails != null) {
                        // create authentication
                        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities()
                        );
                        // set authentication
                        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        // put authentication into context holder
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    }
                }
            } catch (Exception e) {
                logger.debug("Check token failed {}", e.getMessage());
            }
        }

        chain.doFilter(request, response);

    }

}
SecurityConfig

說明:該類是security的配置類,通過該類可以控制資源訪問權(quán)限,通過什么方式進行驗證用戶權(quán)限

package demo.demo1.auth.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public AuthFilter authorizationFilterBean() throws Exception {
        return new AuthFilter();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 禁用csrf
                .csrf().disable()
                // 因為是用的jwt所以不需要session                       .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 運行auth路徑訪問
                .antMatchers("/auth").permitAll()
                // 設(shè)置允許訪問的資源
                .antMatchers("/webjars/**").permitAll()
                .antMatchers(
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources",
                        "/swagger-resources/configuration/security",
                        "/swagger-ui.html",
                        "/swagger-ui.html",
                        "/v2/*",
                        "/user"
                ).permitAll()
                .anyRequest().authenticated();

        // 設(shè)置security過濾器
        http
                .addFilterBefore(authorizationFilterBean(), UsernamePasswordAuthenticationFilter.class);

        http.headers().cacheControl();
    }

    /**
     * 設(shè)置用戶權(quán)限驗證方式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder amb) throws Exception {
        amb.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    // 裝載BCrypt密碼編碼器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
UserDetailsImpl

說明:該類實現(xiàn)了security的UserDetails,進行自定義驗證用戶驗證用戶

值得注意的是,在轉(zhuǎn)換的時候user role前綴必須為ROLE_,否則security會返回403狀態(tài)碼(我在這炸了一天)

package demo.demo1.auth.security;

import demo.demo1.User.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class UserDetailsImpl implements UserDetails {

    private User user;

    public UserDetailsImpl(User user) {
        this.user = user;
    }

    //將你自定義的用戶角色轉(zhuǎn)換為security的user role
    //值得注意的是,在轉(zhuǎn)換的時候user role前綴必須為ROLE_,否則security會返回403狀態(tài)碼
    private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> roles) {

        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(role))
                .collect(Collectors.toList());

    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }


    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        List<String> roles = new ArrayList<String>() {{ add(user.getRole());}};
        if (roles == null) {
            roles = new ArrayList<String>();
        }
        return mapToGrantedAuthorities(roles);
    }

    //必須要有,可以自定義,判斷用戶是否被禁用
    @Override
    public boolean isEnabled() { return true; };

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }
}
UserDetailsServiceImpl

說明:該類繼承了UserDetailsService,自定義security用戶驗證,只需要實現(xiàn)loadUserByUsername方法

該方法正常寫法就如下所示,一般不需要修改

package demo.demo1.auth.security;

import demo.demo1.User.model.User;
import demo.demo1.User.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

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

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        
        try {
            User user = userService.getUserByUserName(username);
            if (user != null) {
                return new UserDetailsImpl(user);
            } else {
                throw new UsernameNotFoundException("username not found.");
            }
        } catch (UsernameNotFoundException e) {
            throw e;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new UsernameNotFoundException(e.getMessage());
        }
        
    }
}
UserController

說明:用來驗證security的一個例子

使用@PreAuthorize("hasRole('ROLE_SENIOR')")注解限制只能ROLE_SENIOR權(quán)限的用戶訪問該接口

這里post接口沒做限制方便實驗的時候可以自定義用戶

package demo.demo1.User.controller;


import demo.demo1.User.model.User;
import demo.demo1.User.service.UserService;
import demo.demo1.auth.security.SecurityConfig;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/user")
@Api(
        value = "/user",
        description = "用戶API"
)
public class UserController {

    @Autowired
    UserService userService;

    @Autowired
    SecurityConfig securityConfig;

    @RequestMapping(value = "" , method = RequestMethod.GET)
    @PreAuthorize("hasRole('ROLE_SENIOR')")
    @ApiOperation(
            value = "get all User",
            code = 201,
            consumes = "application/json",
            produces = "application/json"
    )
    public List<User> getAllUser() {
        return userService.getAllUser();
    }

    @RequestMapping(value = "/{id}" , method = RequestMethod.GET)
    @PreAuthorize("hasRole('ROLE_SENIOR')")
    @ApiOperation(
            value = "get one user by user id",
            code = 201,
            consumes = "application/json",
            produces = "application/json"
    )
    public User getOneUser(
            @ApiParam(value = "用戶UUID") @PathVariable UUID id
    ) {
        return userService.getUserById(id);
    }

    @RequestMapping(value = "" , method = RequestMethod.POST)
    //@PreAuthorize("hasRole('ROLE_SENIOR')")
    @ApiOperation(
            value = "create user",
            code = 201,
            consumes = "application/json",
            produces = "application/json"
    )
    public void create(
            @RequestBody User user
    ) throws Exception{

        user.setUsername(user.getUsername().trim());
        user.setPassword(user.getPassword().trim());
        user.setId(UUID.randomUUID());
        user.setEmail(user.getEmail().trim());

        // encode password
        BCryptPasswordEncoder passwordEncoder = (BCryptPasswordEncoder) securityConfig.passwordEncoder();
        user.setPassword(passwordEncoder.encode(user.getPassword()));

        userService.create(user);
    }

}
UserService

說明:user的service層,不需要多說

package demo.demo1.User.service;

import demo.demo1.User.model.User;
import demo.demo1.User.model.UserRole;
import demo.demo1.User.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User create(User user) throws Exception{

        //check that the username already exists
        if(user.getUsername() == null) {
            throw new Exception("User name can not null");
        }
        if(userRepository.findByUsername(user.getUsername()) != null) {
            throw new Exception(String.format("User %S alrady exist" , user.getUsername()));
        }

        //check userRole
        if(user.getRole() != null) {
            if (!user.getRole().equals(UserRole.ROLE_LOWER.getValue()) && !user.getRole().equals(UserRole.ROLE_SENIOR.getValue()) && !user.getRole().equals(UserRole.ROLE_INTERMEDIATE.getValue())) {
                throw new Exception(String.format("User Role %s is invalid", user.getRole()));
            }
        } else {
            throw new Exception("User role can not null");
        }

        return userRepository.save(user);
    }

    public User getUserById(UUID id) {

        return userRepository.findById(id).get();

    }

    public User getUserByUserName(String name) {

        return userRepository.findByUsername(name);

    }

    public List<User> getAllUser() {

        return userRepository.findAll();

    }

}
User

說明:user bean

這里的set,get可用@Date注解,但是這里我用的builder模式,在寫代碼的時候service層會報紅,沒有安全感,所以都寫上了

package demo.demo1.User.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.validator.constraints.NotBlank;

import javax.persistence.*;
import javax.persistence.Column;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Set;
import java.util.UUID;

@Entity
@Table(name = "DEMO_USER" , indexes = {
        @Index(name = "IDX_USER" , columnList = "ID,USERNAME")
})
public class User {

    public User(){};

    public User(Builder builder) {
        setId(builder.id);
        setUsername(builder.username);
        setPassword(builder.password);
        setRole(builder.role);
        setEmail(builder.email);
    }

    @Id
    @Column(name = "ID")
    @org.hibernate.annotations.Type(type = "org.hibernate.type.PostgresUUIDType")
    @ApiModelProperty(value = "用戶ID", required = false, example = "876C2203-7472-44E8-9EB6-13CF372D326C")
    private UUID id;

    @Column(name = "USERNAME" , length = 60)
    @NotBlank(message = "error.not_blank")
    @Size(min = 1 , max = 50 , message = "error.size")
    @ApiModelProperty(value = "用戶名,長度1~50", required = true, example = "username")
    private String username;

    @Column(name = "PASSWORD", length = 60)
    @NotBlank( message = "error.not_blank")
    @Size(min = 1, max = 60 , message = "error.size")
    @ApiModelProperty(value = "密碼,長度1~25", required = true, example = "r00tme")
    private String password;

    @Column(name = "ROLE" , length = 20)
    @NotNull
    private String role;

    @Column(name = "EMAIL" , length = 60)
    @Size(min = 1, max = 320, message = "error.size")
    @ApiModelProperty(value = "郵箱,長度1~60", required = true, example = "xxx@xxx.com")
    private String email;


    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public static final class Builder {
        private UUID id;
        private String username;
        private String password;
         private String role;
        private String email;

        public Builder setId(UUID val) {
            this.id = val;
            return this;
        }

        public Builder setUsername(String val) {
            this.username = val;
            return this;
        }

        public Builder setPassword(String val) {
            this.password = val;
            return this;
        }

        public Builder setRole(String val) {
            this.role = val;
            return this;
        }


        public Builder setEmail(String val) {
            this.email = val;
            return this;
        }

        public User build() { return new User(this); }

    }

 }
UserRepository
package demo.demo1.User.repository;

import demo.demo1.User.model.User;
import demo.demo1.User.model.UserRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import java.util.UUID;

@Repository
public interface UserRepository extends JpaRepository<User, UUID>, JpaSpecificationExecutor {

    User findByUsername(String username);

}

數(shù)據(jù)流程

spring boot 集成jwt security認證大概流程(轉(zhuǎn)自http://www.reibang.com/p/ca4cebefd1cc

img

首先是左邊一張圖,通過登陸接口獲取token儿礼,該接口是任何權(quán)限都能個訪問的

http請求中攜帶username咖杂,password參數(shù)

經(jīng)過security過濾器或者自定義的過濾器(AuthFilter),驗證是否有權(quán)限訪問該接口

在authController中檢查用戶

根據(jù)登錄的用戶生成相應(yīng)的token蚊夫,將token放在response的head中返回

然后是右邊一張圖诉字,說的是如何通過攜帶token訪問接口

在請求的http resquest中加入在登錄是獲取的token參數(shù)

http request經(jīng)過jwtfile驗證,判斷是否是一個合法的token

將token解析出來獲取用戶信息

http request經(jīng)過自定義security authfile過濾

進入資源認證器知纷,判斷是否有權(quán)限訪問請求的接口

代碼github

https://github.com/wheijxiaotbai/HXB_Knowledge/tree/springboot_jwt_security_demo

如何運行
  • postgresql

    首先你得準(zhǔn)備一個端口為5433的postgresql,用戶名和密碼為security,db為demo_security

    如果你恰巧安裝了docker,請使用以下命令在本地構(gòu)建一個postgresql鏡像,參數(shù)已經(jīng)給出,無需進行其他操作

    run -d --name demo_security --restart always -p 5433:5432 -e TZ=Asia/Shanghai -e POSTGRES_USER=security -e POSTGRES_PASSWORD=security -e POSTGRES_DB=demo_security -v /srv/hxb/postgresql/data:/var/lib/postgresql/data postgres:alpine

    這里之所以postgresql端口配置的為5433是因為demo中的配置文件指定了5433,你可以通過修改demo中的配置文件進行修改

  • 通過開發(fā)工具打開gradle項目,點擊運行即可

  • 在瀏覽器訪問127.0.0.1:8080


    demo1_1.png
  • 在demo中沒有對創(chuàng)建用戶的接口做權(quán)限限制,方便自定義用戶進行測試

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壤圃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屈扎,更是在濱河造成了極大的恐慌埃唯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹰晨,死亡現(xiàn)場離奇詭異墨叛,居然都是意外死亡,警方通過查閱死者的電腦和手機模蜡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門漠趁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忍疾,你說我怎么就攤上這事闯传。” “怎么了卤妒?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵甥绿,是天一觀的道長字币。 經(jīng)常有香客問我,道長共缕,這世上最難降的妖魔是什么洗出? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮图谷,結(jié)果婚禮上翩活,老公的妹妹穿的比我還像新娘。我一直安慰自己便贵,他們只是感情好菠镇,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著承璃,像睡著了一般利耍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绸硕,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天堂竟,我揣著相機與錄音,去河邊找鬼玻佩。 笑死,一個胖子當(dāng)著我的面吹牛席楚,可吹牛的內(nèi)容都是我干的咬崔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼烦秩,長吁一口氣:“原來是場噩夢啊……” “哼垮斯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起只祠,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兜蠕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抛寝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熊杨,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年盗舰,在試婚紗的時候發(fā)現(xiàn)自己被綠了晶府。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡钻趋,死狀恐怖川陆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛮位,我是刑警寧澤较沪,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布鳞绕,位于F島的核電站,受9級特大地震影響尸曼,放射性物質(zhì)發(fā)生泄漏们何。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一骡苞、第九天 我趴在偏房一處隱蔽的房頂上張望垂蜗。 院中可真熱鬧,春花似錦解幽、人聲如沸贴见。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽片部。三九已至,卻和暖如春霜定,著一層夾襖步出監(jiān)牢的瞬間档悠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工望浩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辖所,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓磨德,卻偏偏與公主長得像缘回,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子典挑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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