SpringBoot集成Shiro闸度、JWT 進(jìn)行請(qǐng)求認(rèn)證和鑒權(quán)

什么是JWT?

JSON Web Token(JWT)是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519)侣肄,它定義了一種緊湊且獨(dú)立的方式,可以在各方之間作為JSON對(duì)象安全地傳輸信息印屁。此信息可以通過數(shù)字簽名進(jìn)行驗(yàn)證和信任循捺。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰對(duì)進(jìn)行簽名。
雖然JWT可以加密以在各方之間提供保密雄人,但我們將專注于簽名令牌从橘。簽名令牌可以驗(yàn)證其中包含的聲明的完整性磁餐,而加密令牌則隱藏其他方的聲明休傍。當(dāng)使用公鑰/私鑰對(duì)簽署令牌時(shí)岳掐,簽名還證明只有持有私鑰的一方是簽署私鑰的一方悟狱。

使用場(chǎng)景

特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場(chǎng)景孕豹。JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息舆床, 以便于從資源服務(wù)器獲取資源缕粹,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息登夫,該token也可直接被用于認(rèn)證很钓,也可被加密香府。
JWT是由三段信息構(gòu)成的董栽,將這三段信息文本用.鏈接一起就構(gòu)成了Jwt字符串。
格式如下:

xxxxx.yyyyy.zzzzz

就像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT的構(gòu)成

第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似于飛機(jī)上承載的物品)企孩,第三部分是簽證(signature)锭碳。

header 頭部

標(biāo)頭通常由兩部分組成:令牌的類型,即JWT勿璃,以及正在使用的簽名算法擒抛,例如HMAC SHA256或RSA。

這里的加密算法是單向函數(shù)散列算法补疑,常見的有MD5歧沪、SHA、HAMC癣丧。這里使用基于密鑰的Hash算法HMAC生成散列值槽畔。

MD5 message-digest algorithm 5 (信息-摘要算法)縮寫,廣泛用于加密和解密技術(shù)胁编,常用于文件校驗(yàn)厢钧。校驗(yàn)?不管文件多大嬉橙,經(jīng)過MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm早直,安全散列算法),數(shù)字簽名等密碼學(xué)應(yīng)用中重要的工具市框,安全性高于MD5霞扬。

HMAC (Hash Message Authentication Code,散列消息鑒別碼枫振,基于密鑰的Hash算法的認(rèn)證協(xié)議喻圃。用公開函數(shù)和密鑰產(chǎn)生一個(gè)固定長(zhǎng)度的值作為認(rèn)證標(biāo)識(shí),用這個(gè)標(biāo)識(shí)鑒別消息的完整性粪滤。常用于接口簽名驗(yàn)證
完整的頭部就像下面這樣的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后將頭部進(jìn)行base64加密(該加密是可以對(duì)稱解密的),構(gòu)成了第一部分

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload

payload 載荷

令牌的第二部分是有效負(fù)載斧拍,其中包含聲明。聲明是關(guān)于實(shí)體(通常是用戶)和其他數(shù)據(jù)的聲明杖小。聲明有三種類型:注冊(cè)肆汹,公開和私人。
載荷就是存放有效信息的地方予权,這些有效信息包含三個(gè)部分:

標(biāo)準(zhǔn)中注冊(cè)的聲明
公共的聲明
私有的聲明

標(biāo)準(zhǔn)中注冊(cè)的聲明 (建議但不強(qiáng)制使用) :

iss: jwt簽發(fā)者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時(shí)間昂勉,這個(gè)過期時(shí)間必須要大于簽發(fā)時(shí)間
nbf: 定義在什么時(shí)間之前,該jwt都是不可用的.
iat: jwt的簽發(fā)時(shí)間
jti: jwt的唯一身份標(biāo)識(shí)扫腺,主要用來作為一次性token,從而回避重放攻擊岗照。

公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷?/p>

私有聲明是提供者和消費(fèi)者所共同定義的聲明谴返,一般不建議存放敏感信息煞肾,因?yàn)閎ase64是對(duì)稱解密的咧织,意味著該部分信息可以歸類為明文信息嗓袱。

定義一個(gè)payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后將其進(jìn)行base64加密,得到Jwt的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

請(qǐng)注意习绢,對(duì)于簽名令牌渠抹,此信息雖然可以防止被篡改,但任何人都可以讀取闪萄。除非加密梧却,否則不要將秘密信息放在JWT的有效負(fù)載或頭元素中。

signature 簽名

要?jiǎng)?chuàng)建簽名部分败去,您必須采用編碼標(biāo)頭放航,編碼的有效負(fù)載,鹽值圆裕,標(biāo)頭中指定的算法广鳍,并對(duì)其進(jìn)行簽名。
這個(gè)簽證信息由三部分組成:

header (base64后的)  
payload (base64后的)  
secret  

這個(gè)部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串吓妆, 然后通過header中聲明的加密方式進(jìn)行加鹽secret組合加密赊时,然后就構(gòu)成了jwt的第三部分。
例如行拢,如果要使用HMAC SHA256算法祖秒,將按以下方式創(chuàng)建簽名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

javascript例子如下:

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

將這三部分用.連接成一個(gè)完整的字符串,構(gòu)成了最終的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服務(wù)器端的,jwt的簽發(fā)生成也是在服務(wù)器端的舟奠,secret就是用來進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證竭缝, 所以,它就是你服務(wù)端的私鑰沼瘫,在任何場(chǎng)景都不應(yīng)該流露出去抬纸。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。

與SpringBoot shiro集成

  1. 導(dǎo)入所需jar包
 compile 'com.auth0:java-jwt:3.4.0'
compile 'org.apache.shiro:shiro-spring:1.4.0'
compile 'org.springframework.boot:spring-boot-starter-aop:2.0.4.RELEASE'
compile group: 'com.alibaba', name: 'druid', version: '1.1.10'
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.46'
compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.3.2'
compile group: 'org.springframework.boot', name: 'spring-boot-devtools', version: '2.0.4.RELEASE'

使用Mybatis+Shiro做權(quán)限驗(yàn)證晕鹊,這里有一個(gè)坑要注意下松却,AOP jar包一定要導(dǎo)入,不然驗(yàn)證權(quán)限注解將失效溅话。
導(dǎo)致不會(huì)進(jìn)入doGetAuthorizationInfo()方法晓锻。

  1. 實(shí)現(xiàn)JWT 請(qǐng)求驗(yàn)證
    主要思路

首先用戶登錄成功后,利用官方的JWT包飞几,配置生成并返回一段token砚哆,接著配置JWT的檢驗(yàn)token過濾器,讓請(qǐng)求都需要驗(yàn)證是否加上此token在請(qǐng)求頭上屑墨。
沒有則會(huì)跳到無授權(quán)躁锁。

利用JWT包纷铣,構(gòu)造生成TOKEN和檢驗(yàn)token的方法。

public class JWTUtil {
    /**
     * 過期時(shí)間 24 小時(shí)
     */
    private static final long EXPIRE_TIME = 60 * 24 * 60 * 1000;
    /**
     * 密鑰战转,注意這里如果真實(shí)用到搜立,應(yīng)當(dāng)設(shè)置到復(fù)雜點(diǎn),相當(dāng)于私鑰的存在槐秧。如果被人拿到啄踊,想到于它可以自己制造token了。
     */
    private static final String SECRET = "LIAODASHUAI";

    /**
     * 生成 token, 5min后過期
     *
     * @param username 用戶名
     * @return 加密的token string
     */
    public static String createToken(String username) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(SECRET);
        // 附帶username信息
        return JWT.create()
                .withClaim("username", username)
                //到期時(shí)間
                .withExpiresAt(date)
                //創(chuàng)建一個(gè)新的JWT刁标,并使用給定的算法進(jìn)行標(biāo)記
                .sign(algorithm);
    }

    /**
     * 校驗(yàn) token 是否正確
     *
     * @param token    密鑰
     * @param username 用戶名
     * @return 是否正確 boolean
     */
    public static boolean verify(String token, String username) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            //在token中附帶了username信息
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //驗(yàn)證 token
            verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 獲得token中的信息颠通,無需secret解密也能獲得
     *
     * @param token the token
     * @return token中包含的用戶名 username
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }
}

重新實(shí)現(xiàn)AuthenticationToken類,讓其存放token膀懈,便于校驗(yàn)顿锰。

public class JWTToken implements AuthenticationToken {
    private String token;

    /**
     * Instantiates a new Jwt token.
     *
     * @param token the token
     */
    public JWTToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

接著到了關(guān)鍵的JWT過濾器實(shí)現(xiàn),此過濾器繼承實(shí)現(xiàn)了BasicHttpAuthenticationFilter的部分方法启搂。
主要作用是:

檢驗(yàn)請(qǐng)求頭是否帶有 token,req.getHeader("token")!=null
如果帶有 token硼控,執(zhí)行 shiro 的 login() 方法,將 token 提交到 Realm 中進(jìn)行檢驗(yàn)狐血;如果沒有 token淀歇,說明當(dāng)前狀態(tài)為游客狀態(tài)(或者其他一些不需要進(jìn)行認(rèn)證的接口)
如果在 token 校驗(yàn)的過程中出現(xiàn)錯(cuò)誤,如 token 校驗(yàn)失敗匈织,那么我會(huì)將該請(qǐng)求視為認(rèn)證不通過浪默,則重定向到 /unauthorized/**
public class JWTFilter extends BasicHttpAuthenticationFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 如果帶有 token,則對(duì) token 進(jìn)行檢查缀匕,否則直接通過
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        //判斷請(qǐng)求的請(qǐng)求頭是否帶上 "token"
        if (isLoginAttempt(request, response)) {
            //如果存在纳决,則進(jìn)入 executeLogin 方法執(zhí)行登入,檢查 token 是否正確
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                //token 錯(cuò)誤
                responseError(response, e.getMessage());
            }
        }
        //如果請(qǐng)求頭不存在 Token乡小,則可能是執(zhí)行登陸操作或者是游客狀態(tài)訪問阔加,無需檢查 token,直接返回 true
        return true;
    }

    /**
     * 判斷用戶是否想要登入满钟。
     * 檢測(cè) header 里面是否包含 Token 字段
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("token");
        return token != null;
    }

    /**
     * 執(zhí)行登陸操作
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");
        JWTToken jwtToken = new JWTToken(token);
        // 提交給realm進(jìn)行登入胜榔,如果錯(cuò)誤他會(huì)拋出異常并被捕獲
        getSubject(request, response).login(jwtToken);
        // 如果沒有拋出異常則代表登入成功,返回true
        return true;
    }

    /**
     * 對(duì)跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求湃番,這里我們給option請(qǐng)求直接返回正常狀態(tài)
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 將非法請(qǐng)求跳轉(zhuǎn)到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //設(shè)置編碼夭织,否則中文字符在重定向時(shí)會(huì)變?yōu)榭兆址?            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/unauthorized/" + message);
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }
}

繼承AuthorizingRealm,實(shí)現(xiàn)用戶授權(quán)的驗(yàn)證和權(quán)限的驗(yàn)證

public class CustomRealm extends AuthorizingRealm {
    @Autowired
    UserInfoMapper userInfoMapper;
    @Autowired
    RoleMapper roleMapper;

    /**
     * 必須重寫此方法吠撮,不然會(huì)報(bào)錯(cuò)
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 默認(rèn)使用此方法進(jìn)行用戶名正確與否驗(yàn)證尊惰,錯(cuò)誤拋出異常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("————身份認(rèn)證方法————");
        String token = (String) authenticationToken.getCredentials();
        // 解密獲得username,用于和數(shù)據(jù)庫進(jìn)行對(duì)比
        String username = JWTUtil.getUsername(token);
        if (username == null || !JWTUtil.verify(token, username)) {
            throw new AuthenticationException("token認(rèn)證失斉拧题禀!");
        }
        UserInfo userInfo = userInfoMapper.selectByName(username);
        if (userInfo == null) {
            throw new AuthenticationException("該用戶不存在!");
        }
        if (userInfo.getState() == 1) {
            throw new AuthenticationException("該用戶已被封號(hào)膀捷!");
        }
        return new SimpleAuthenticationInfo(token, token, "MyRealm");
    }

    /**
     * 只有當(dāng)需要檢測(cè)用戶權(quán)限的時(shí)候才會(huì)調(diào)用此方法迈嘹,例如checkRole,checkPermission之類的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("————權(quán)限認(rèn)證————");
        String username = JWTUtil.getUsername(principals.toString());
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 此處最好使用緩存提升速度
        UserInfo userInfo = userInfoMapper.selectByName(username);
        userInfo = userInfoMapper.selectUserOfRole(userInfo.getUid());
        if (userInfo == null || userInfo.getRoleList().isEmpty()) {
            return authorizationInfo;
        }
        for (Role role : userInfo.getRoleList()) {
            authorizationInfo.addRole(role.getRole());
            role = roleMapper.selectRoleOfPerm(role.getId());
            if (role == null || role.getPermissions().isEmpty()) {
                continue;
            }
            for (Permission p : role.getPermissions()) {
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }
}

配置ShiroConfig,將自定義的過濾器設(shè)置進(jìn)去

@Configuration
public class ShiroConfig {
    /**
     * 先走 filter 担孔,然后 filter 如果檢測(cè)到請(qǐng)求頭存在 token江锨,則用 token 去 login,走 Realm 去驗(yàn)證
     *
     * @param securityManager the security manager
     * @return the shiro filter factory bean
     */
    @Bean
    public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        Map<String, Filter> filterMap = new HashMap<>();
        //設(shè)置我們自定義的JWT過濾器
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);
        // 設(shè)置無權(quán)限時(shí)跳轉(zhuǎn)的 url;
        factoryBean.setUnauthorizedUrl("/unauthorized/無權(quán)限");
        Map<String, String> filterRuleMap = new HashMap<>();
        //訪問/login和/unauthorized 不需要經(jīng)過過濾器
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/unauthorized/**", "anon");
        // 所有請(qǐng)求通過我們自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 訪問 /unauthorized/** 不通過JWTFilter
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 注入 securityManager
     *
     * @return the security manager
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設(shè)置自定義 realm.
        securityManager.setRealm(customRealm());
        /*
         * 關(guān)閉shiro自帶的session糕篇,詳情見文檔
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    @Bean
    public CustomRealm customRealm() {
        return new CustomRealm();
    }

    /**
     * 開啟shiro aop注解支持. 使用代理方式; 所以需要開啟代碼支持;
     *
     * @param securityManager 安全管理器
     * @return 授權(quán)Advisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

配值好后,接入swagger2,方便測(cè)試接口,配置swagger時(shí)酌心,設(shè)置一個(gè)header參數(shù)的token,方便我們調(diào)用拌消。

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    /**
     * Create rest api docket.
     *
     * @return the docket
     */
    @Bean
    public Docket createRestApi() {
        ParameterBuilder tokenPar = new ParameterBuilder();
        List<Parameter> pars = new ArrayList<>();
        //header中的token參數(shù)非必填,傳空也可以
        tokenPar.name("token").description("請(qǐng)求接口所需Token")
                .modelRef(new ModelRef("string")).parameterType("header")
                .required(false).build();
        pars.add(tokenPar.build());
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(metaData())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.dashuai.learning.jwt.api"))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(pars);
    }

    private ApiInfo metaData() {
        return new ApiInfoBuilder()
                .title("集成JWT API文檔")
                .description("描述")
                .termsOfServiceUrl("")
                .contact(new Contact("dashuai", "https://github.com/liaozihong", "15017263266@173.com"))
                .version("1.0")
                .build();
    }
}

調(diào)用授權(quán)api安券,登錄成功墩崩,會(huì)返回token:

image

拿到返回的token,調(diào)用接口侯勉,可以看到成功調(diào)用:
image

去掉token或使用錯(cuò)誤的token將會(huì)報(bào)token認(rèn)證失旔谐铩:
image

JWT 弊端,如果使用JWT來著做會(huì)話管理址貌,那么注銷铐拐、改密、續(xù)簽等問題练对,你將要慢慢爬坑遍蟋。
具體可參考大佬寫的一篇文章:http://blog.didispace.com/learn-how-to-use-jwt-xjf/

Demo源碼:https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-JWT

參考鏈接:
JWT官方鏈接
使用JWt帶來的一些問題
https://www.xncoding.com/2017/07/09/spring/sb-jwt.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市螟凭,隨后出現(xiàn)的幾起案子虚青,更是在濱河造成了極大的恐慌,老刑警劉巖螺男,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棒厘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡下隧,警方通過查閱死者的電腦和手機(jī)奢人,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汪拥,“玉大人达传,你說我怎么就攤上這事。” “怎么了宪赶?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵宗弯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我搂妻,道長(zhǎng)蒙保,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任欲主,我火速辦了婚禮邓厕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扁瓢。我一直安慰自己详恼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布引几。 她就那樣靜靜地躺著昧互,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伟桅。 梳的紋絲不亂的頭發(fā)上敞掘,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音楣铁,去河邊找鬼玖雁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛盖腕,可吹牛的內(nèi)容都是我干的赫冬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赊堪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼面殖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哭廉,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤脊僚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后遵绰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辽幌,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年椿访,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乌企。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡成玫,死狀恐怖加酵,靈堂內(nèi)的尸體忽然破棺而出拳喻,到底是詐尸還是另有隱情,我是刑警寧澤猪腕,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布冗澈,位于F島的核電站,受9級(jí)特大地震影響陋葡,放射性物質(zhì)發(fā)生泄漏亚亲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一腐缤、第九天 我趴在偏房一處隱蔽的房頂上張望捌归。 院中可真熱鬧,春花似錦岭粤、人聲如沸惜索。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽门扇。三九已至,卻和暖如春偿渡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霸奕。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工溜宽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人质帅。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓适揉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親煤惩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫉嘀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355