第二章:最新版本spring boot2.x整合jwt認(rèn)證

引言:什么是jwt

Json Web Token(JWT):JSON網(wǎng)絡(luò)令牌,是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而制定的一種基于JSON的開放標(biāo)準(zhǔn)((RFC 7519)宫仗。JWT是一個(gè)輕便的安全跨平臺傳輸格式贮庞,定義了一個(gè)緊湊的自包含的方式用于通信雙方之間以 JSON 對象行使安全的傳遞信息示惊。因?yàn)閿?shù)字簽名的存在急迂,這些信息是可信的匿值。

JWT的組成

jwt含有三個(gè)部分

  • 頭部(header)
  • 載荷(payload)
  • 簽證(signature)
頭部(header)

頭部一般有兩部分信息:類型垄琐、加秘的算法(通常使用HMAC SHA256)

載荷(payload)

該部分一般存放一些有效的信息边酒。jwt的標(biāo)準(zhǔn)定義包含五個(gè)字段:

  • iss: jwt的簽發(fā)者
  • sub:jwt所面向的用戶
  • aud:接收該jwt的一方
  • exp(expires):什么時(shí)候過期,這里是一個(gè)Unit的時(shí)間戳
  • iat(issued at):在什么時(shí)候簽發(fā)的
簽證(signature)

jwt最后一個(gè)部分狸窘。該部分是使用了HS256加密后的數(shù)據(jù)墩朦;包含了三個(gè)部分:

  • header(base64后的)
  • payload(base64后的)
  • secret 私鑰
    secret是保存在服務(wù)器端(server)的,jwt的簽發(fā)生成也是在服務(wù)器端的翻擒,secret就是用來進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證氓涣。所以,它就是服務(wù)端的密鑰陋气,z在任何場景都不應(yīng)該流露出去劳吠。一旦客戶端得知這個(gè)secret,那就有客戶端自我簽發(fā)jwt的安全危險(xiǎn)了恩伺。

jwt特點(diǎn)

  • 緊湊: 意味著這個(gè)字符串很小赴背,甚至可以放在URL參數(shù),POST Parameter中以Http Header的方式傳輸。
  • 自包含: 傳輸?shù)淖址芏嘈畔⒒思裕瑒e人拿到以后就不需要多次訪問數(shù)據(jù)庫獲取信息燃观,并且通過其中的信息就可以知道加密類型和方式(當(dāng)然解密需要公鑰和密鑰)。

如何使用jwt

在身份鑒定的實(shí)現(xiàn)中便瑟,傳統(tǒng)的方法是在服務(wù)端存儲一個(gè) session缆毁,給客戶端返回一個(gè) cookie,而使用JWT之后到涂,當(dāng)用戶使用它的認(rèn)證信息登錄系統(tǒng)之后脊框,會(huì)返回給用戶一個(gè)JWT(token), 用戶只需要本地保存該 token(通常使用localStorage践啄,也可以使用cookie)即可浇雹。

當(dāng)用戶希望訪問一個(gè)受保護(hù)的路由或者資源的時(shí)候,通常應(yīng)該在 Authorization頭部使用 Bearer 模式添加JWT屿讽,其內(nèi)容格式:

Authorization: Bearer <token>

因?yàn)橛脩舻臓顟B(tài)在服務(wù)端內(nèi)容中是不存儲的昭灵,所以這是一種無狀態(tài)的認(rèn)證機(jī)制。服務(wù)端的保護(hù)路由將會(huì)檢查請求頭 Authorization 中的JWT信息伐谈,如果合法烂完,則允許用戶的行為。由于JWT是自包含的诵棵,因此抠蚣,減少了需要查詢數(shù)據(jù)庫的需要。

JWT的這些特征使得我們可以完全依賴無狀態(tài)的特性提供數(shù)據(jù)API服務(wù)履澳。因?yàn)镴WT并不使用Cookie的嘶窄,所以你可以在任何域名提供你的API服務(wù)而不需要擔(dān)心跨域資源共享問題(CORS)
下面的序列圖展示了該過程:

流程圖

中文流程介紹:

  1. 用戶使用賬號和密碼發(fā)出POST登錄請求;
  2. 服務(wù)器使用私鑰創(chuàng)建一個(gè)JWT距贷;
  3. 服務(wù)器返回這個(gè)JWT給瀏覽器护侮;
  4. 瀏覽器將該JWT串放在請求頭中向服
    務(wù)器發(fā)送請求;
  5. 服務(wù)器驗(yàn)證該JWT储耐;
  6. 返回響應(yīng)的資源給瀏覽器。

說了這么多JWT到底如何應(yīng)用到我們的項(xiàng)目中滨溉,下面我們就使用SpringBoot 結(jié)合 JWT完成用戶的登錄驗(yàn)證什湘。

應(yīng)用

  • 初次登錄生成jwt流程圖


    登錄生成jwt流程圖
  • 用戶訪問資源流程圖


    用戶訪問資源流程圖

集成

壞境:

  • spring boot 2.2.4.RELEASE
  • jjwt 0.9.1
    其它工具版本可以查看我gitee上pom.xml的依賴。

下面通過代碼來實(shí)現(xiàn)用戶認(rèn)證的功能晦攒,博主這里主要采用Spring Boot與JWT整合的方式實(shí)現(xiàn)
1闽撤、我的pom.xml相關(guān)依賴版本代碼

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jinzheyi</groupId>
    <artifactId>jwt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>jwt</name>
    <description>Spring Boot集成jwt</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--引入jwt依賴-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、在工程 application.yml 配置文件中添加JWT的配置信息:

##jwt配置
audience:
  #代表這個(gè)jwt的接收對象脯颜,存入audience
  clientId: 098f6bcd4621d373cade4e832627b4f6
  #密鑰哟旗,經(jīng)過Base64加密,可自行替換
  base64Secret: MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=
  # JWT的簽發(fā)主體,存入issuer
  name: restapiuser
  # 過期時(shí)間闸餐,時(shí)間戳
  expiresSecond: 172800

server:
  port: 8081

3饱亮、新建配置信息的實(shí)體類,以便獲取JWT配置:

package com.jinzheyi.jwt.entity;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 *@Description 創(chuàng)建的關(guān)于yml文件中jwt配置屬性的實(shí)體類
 *@Author jinzheyi
 *@Date 2020/2/15 22:49
 *@version 0.1
 */
@Data
@ConfigurationProperties(prefix = "audience")
@Component
public class Audience {

    //客戶端id
    private String clientId;
    //經(jīng)過base64加密后密鑰
    private String base64Secret;
    //簽發(fā)主題
    private String name;
    //過期時(shí)間
    private int expiresSecond;
}

JWT驗(yàn)證主要是通過過濾器驗(yàn)證舍沙,所以我們需要添加一個(gè)攔截器來演請求頭中是否包含有后臺頒發(fā)的 token
4近上、創(chuàng)建JWT驗(yàn)證攔截器(進(jìn)行了一層封裝):

package com.jinzheyi.jwt.config.interceptor;

import com.jinzheyi.jwt.common.Constant;
import com.jinzheyi.jwt.common.annotation.JwtIgnore;
import com.jinzheyi.jwt.common.exception.CustomException;
import com.jinzheyi.jwt.common.response.ResultCode;
import com.jinzheyi.jwt.entity.Audience;
import com.jinzheyi.jwt.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *@Description 封裝好的jwt攔截器處理類
 *@Author jinzheyi
 *@Date 2020/2/16 14:00
 *@version 0.1
 */
@Slf4j
public class JwtInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private Audience audience;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        //忽略帶JwtIgnore注解的請求,不做后續(xù)token認(rèn)證校驗(yàn)
        if (handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
            if (jwtIgnore != null) {
                return true;
            }
        }
        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        //獲取請求頭信息authorization信息
        final String authHeader = request.getHeader(Constant.AUTH_HEADER_KEY);
        log.info("##authHeader = {}",authHeader);
        if (StringUtils.isBlank(authHeader)||!authHeader.startsWith(Constant.TOKEN_PREFIX)) {
            log.info("###用戶未登錄拂铡,請先登錄###");
            throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
        }
        //獲取token
        final String token = authHeader.substring(7);
        if (audience == null) {
            BeanFactory beanFactory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            audience = (Audience) beanFactory.getBean("audience");
        }
        JwtUtil.parseJwt(token, audience.getBase64Secret());
        return true;

    }
}
package com.jinzheyi.jwt.config;

import com.jinzheyi.jwt.config.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 *@Description 全局配置公共類壹无,含攔截器、允許跨域請求處理
 *@Author jinzheyi
 *@Date 2020/2/16 14:01
 *@version 0.1
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 添加攔截器
     * @param interceptorRegistry
     */
    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry){
        //攔截路徑可自行配置多個(gè) 可用感帅,分隔開
        interceptorRegistry.addInterceptor(new JwtInterceptor()).addPathPatterns("/**");
    }

    /**
     * 跨域支持
     * @param corsRegistry
     */
    @Override
    public void addCorsMappings(CorsRegistry corsRegistry){
        corsRegistry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
                .maxAge(3600 * 24);
    }
}

5斗锭、然后我們創(chuàng)建JWT工具類:

package com.jinzheyi.jwt.utils;

import com.jinzheyi.jwt.common.exception.CustomException;
import com.jinzheyi.jwt.common.response.ResultCode;
import com.jinzheyi.jwt.entity.Audience;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;

/**
 *@Description jwt工具類
 *@Author jinzheyi
 *@Date 2020/2/16 14:03
 *@version 0.1
 */
public class JwtUtil {

    //創(chuàng)建日志對象
    private static Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    /**
     * 解析 jwt
     * @param jsonWebToken token
     * @param base64Security
     * @return
     */
    public static Claims parseJwt(String jsonWebToken, String base64Security){
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
                    .parseClaimsJws(jsonWebToken).getBody();
            return claims;
        } catch (ExpiredJwtException e){
            logger.error("===token過期===");
            throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED_EXCEPTION);
        } catch (MalformedJwtException e){
            logger.error("====json web token格式錯(cuò)誤====");
            throw new CustomException(ResultCode.PERMISSION_TOKEN_MALFORMEDJWT_EXCEPTION);
        }catch (Exception e){
            logger.error("=====token解析異常=====");
            throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
        }
    }

    /**
     * 構(gòu)建jwt
     * @param userId 用戶id
     * @param username jwt的所有者
     * @param role 權(quán)限
     * @param audience 配置信息對象
     * @return
     */
    public static String createJwt(String userId, String username, String role, Audience audience){
        try {//使用HS256加密算法
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            Long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
            //生成簽名
            byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(audience.getBase64Secret());
            Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
            //userId是重要的信息,進(jìn)行簡要加密下
            String encryId = Base64Util.encode(userId);
            //添加構(gòu)成JWT的參數(shù)
            JwtBuilder jwtBuilder = Jwts.builder().setHeaderParam("type", "JWT")
                    //可以將基本不重要的對象信息放到claims
                    .claim("role", role)
                    .claim("userId", encryId)
                    .setSubject(username)   //代表這個(gè)jwt的主體失球,即它的所有人
                    .setIssuer(audience.getClientId())  //代表這個(gè)jwt的簽發(fā)主體
                    .setIssuedAt(new Date())    //一個(gè)時(shí)間戳岖是,代表簽發(fā)時(shí)間
                    .setAudience(audience.getName())    //代表jwt的接收對象
                    .signWith(signatureAlgorithm, signingKey);
            //添加token過期時(shí)間
            int expiresMillis = audience.getExpiresSecond();
            if (expiresMillis >= 0) {
                long expMillis = nowMillis + expiresMillis;
                Date expireTime = new Date(expMillis);
                jwtBuilder.setExpiration(expireTime)   //是一個(gè)時(shí)間戳,代表jwt的過期時(shí)間
                        .setNotBefore(now);     //是一個(gè)時(shí)間戳她倘,代表這個(gè)jwt生效的開始時(shí)間璧微,意味在這個(gè)時(shí)間之前驗(yàn)證jwt都是無效的
            }
            return jwtBuilder.compact();
        } catch (Exception e){
            logger.error("生成簽名失敗");
            throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
        }
    }

    /**
     * 獲取用戶
     * @param token
     * @param base64Security
     * @return
     */
    public static String getUsername(String token, String base64Security){
        return parseJwt(token, base64Security).getSubject();
    }

    /**
     * 獲取用戶id
     * @param token
     * @param base64Security
     * @return
     */
    public static String getUserId(String token, String base64Security){
        return Base64Util.decode(parseJwt(token, base64Security).get("userId",String.class));
    }

    /**
     * token是否過期
     * @param token
     * @param base64Security
     * @return
     */
    public static boolean isExpiration(String token, String base64Security){
        return parseJwt(token, base64Security).getExpiration().before(new Date());
    }
}

其中創(chuàng)建jwt工具類的時(shí)候,內(nèi)部使用到的一些封裝方法硬梁,可以到我的gitee分享的代碼里面進(jìn)行查看前硫。
6、添加全局異常處理

package com.jinzheyi.jwt.common.exception;

import com.jinzheyi.jwt.common.response.ResultCode;
import com.jinzheyi.jwt.common.response.ResultData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;

/**
 *@Description 定義全局異常處理類
 *@Author jinzheyi
 *@Date 2020/2/16 13:58
 *@version 0.1
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    public static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 處理自定義異常
     * @param e
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public ResultData handleException(CustomException e){
        // 打印異常信息
        logger.error("### 異常信息:{} ###", e.getMessage());
        return new ResultData(e.getResultCode());
    }

    /**
     * 參數(shù)錯(cuò)誤異常
     * @param e
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public ResultData handleException(Exception e) {
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
            BindingResult result = validException.getBindingResult();
            StringBuffer errorMsg = new StringBuffer();
            if (result.hasErrors()) {
                List<ObjectError> errors = result.getAllErrors();
                errors.forEach(p ->{
                    FieldError fieldError = (FieldError) p;
                    errorMsg.append(fieldError.getDefaultMessage()).append(",");
                    logger.error("### 請求參數(shù)錯(cuò)誤:{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+ "},errorMessage{"+fieldError.getDefaultMessage()+"}");
                });
            }
        } else if (e instanceof BindException) {
            BindException bindException = (BindException)e;
            if (bindException.hasErrors()) {
                logger.error("### 請求參數(shù)錯(cuò)誤: {}", bindException.getAllErrors());
            }
        }
        return new ResultData(ResultCode.PARAM_IS_INVALID);
    }

    /**
     * 處理所有不可知的異常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResultData handleOtherException(Exception e){
        //打印異常堆棧信息
        e.printStackTrace();
        // 打印異常信息
        logger.error("### 不可知的異常:{} ###", e.getMessage());
        return new ResultData(ResultCode.SYSTEM_INNER_ERROR);
    }

}

7荧止、最后添加用戶Controller進(jìn)行測試

package com.jinzheyi.jwt.web.controller;

import com.alibaba.fastjson.JSONObject;
import com.jinzheyi.jwt.common.Constant;
import com.jinzheyi.jwt.common.annotation.JwtIgnore;
import com.jinzheyi.jwt.common.response.ResultData;
import com.jinzheyi.jwt.entity.Audience;
import com.jinzheyi.jwt.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 *@Description 用戶業(yè)務(wù)控制層
 *@Author jinzheyi
 *@Date 2020/2/16 14:03
 *@version 0.1
 */
@Slf4j
@RestController
public class UserController {

    @Autowired
    private Audience audience;

    @PostMapping("/login")
    @JwtIgnore
    public ResultData login(HttpServletResponse response, String username, String password){
        //這里模擬測試屹电,默認(rèn)登錄成功,返回用戶id和角色信息
        String userId = UUID.randomUUID().toString();
        String role = "admin";
        //創(chuàng)建token
        String token = JwtUtil.createJwt(userId, username, role, audience);
        log.info("### 登錄成功, token={} ###", token);
        response.setHeader(Constant.AUTH_HEADER_KEY, Constant.TOKEN_PREFIX + token);
        //將token響應(yīng)給客戶端
        JSONObject result = new JSONObject();
        result.put("token", token);
        return ResultData.successResultData(result);
    }

    @GetMapping("/users")
    public ResultData userList() {
        log.info("### 查詢所有用戶列表 ###");
        return ResultData.successResult();
    }

}

8跃巡、接下來我們使用PostMan工具進(jìn)行測試:
沒有登錄時(shí)候直接訪問:http://localhost:8081/users 接口:

沒有登錄時(shí)獲取用戶數(shù)據(jù)

然后我們?nèi)?zhí)行登錄接口:http://localhost:8081/login?username=zhangsan接口
執(zhí)行登錄接口

此時(shí)我們獲取到了后臺服務(wù)器生成的token危号。將token帶入到獲取用戶的接口中,如圖
帶入token查詢用戶數(shù)據(jù)

注意:這里選擇 Bearer Token類型素邪,就把不要在 Token中手動(dòng)Bearer外莲,postman會(huì)自動(dòng)拼接。
補(bǔ)充:如果帶入錯(cuò)誤的token會(huì)提示token已失效兔朦,或者超時(shí)之后帶入token偷线,會(huì)提示已超時(shí)。攔截器里面已經(jīng)做了判斷沽甥。登錄接口不用驗(yàn)證token是因?yàn)槲覀冊赾ontroller層login方法加了@JwtIgnore注解

作者:金哲一(jinzheyi)【筆名】
本文代碼地址:https://gitee.com/jinzheyi/springboot/tree/master/springboot2.x
本文鏈接:http://www.reibang.com/p/92e96634746d

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末声邦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摆舟,更是在濱河造成了極大的恐慌亥曹,老刑警劉巖邓了,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異媳瞪,居然都是意外死亡骗炉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門材失,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痕鳍,“玉大人,你說我怎么就攤上這事龙巨×簦” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵旨别,是天一觀的道長诗赌。 經(jīng)常有香客問我,道長秸弛,這世上最難降的妖魔是什么铭若? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮递览,結(jié)果婚禮上叼屠,老公的妹妹穿的比我還像新娘。我一直安慰自己绞铃,他們只是感情好镜雨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著儿捧,像睡著了一般荚坞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菲盾,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天颓影,我揣著相機(jī)與錄音,去河邊找鬼懒鉴。 笑死诡挂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的临谱。 我是一名探鬼主播咆畏,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吴裤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起溺健,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤麦牺,失蹤者是張志新(化名)和其女友劉穎钮蛛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剖膳,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魏颓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吱晒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甸饱。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖仑濒,靈堂內(nèi)的尸體忽然破棺而出叹话,到底是詐尸還是另有隱情,我是刑警寧澤墩瞳,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布驼壶,位于F島的核電站,受9級特大地震影響喉酌,放射性物質(zhì)發(fā)生泄漏热凹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一泪电、第九天 我趴在偏房一處隱蔽的房頂上張望般妙。 院中可真熱鬧,春花似錦相速、人聲如沸碟渺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽止状。三九已至,卻和暖如春攒霹,著一層夾襖步出監(jiān)牢的瞬間怯疤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工催束, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留集峦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓抠刺,卻偏偏與公主長得像塔淤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子速妖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354