Spring Security整合JWT袖订,實現(xiàn)單點登錄,So Easy~嗅虏!

前面整理過一篇 SpringBoot Security前后端分離洛姑,登錄退出等返回json數(shù)據(jù),也就是用Spring Security皮服,基于SpringBoot2.1.4 RELEASE前后端分離的情況下楞艾,實現(xiàn)了登陸登出的功能参咙,亮點就在于以JSON的形式接收返回參數(shù)。這個是針對單個后臺服務(wù)的硫眯, 登錄信息都存儲在SecurityContextHolder緩存里蕴侧。如果是兩個或兩個以上的應(yīng)用呢,那該怎么辦两入?Session是不能用了净宵,Cookie自然也不能用,畢竟它倆是一對的裹纳。

曾想過用OAuth2來解決這個問題择葡,但是OAuth2太復(fù)雜,首先理解概念就需要花費一些時間剃氧,而且里面的授權(quán)服務(wù)器敏储、資源服務(wù)器、客戶端等等讓人傻傻分不清朋鞍,還有四種授權(quán)模式已添,要反復(fù)衡量,到底要用哪一種滥酥,概念還沒有扯清楚就開始糾結(jié)使用哪一個了酝碳。從概念入手不是個好主意,也不是個輕松的主意恨狈。在理解OAuth2的過程中疏哗,想到自己的項目是前后端分離的,離不開JSON禾怠,無意中遇見JWT返奉。JWT是什么玩意,咦吗氏,難道是自己苦苦尋求的嗎芽偏?!

那么弦讽,什么是JWT呢污尉?看看專家介紹 阮一峰的網(wǎng)絡(luò)日志,才知道往产,JWT 是JSON Web Token的簡稱被碗,它解決的就是跨域問題》麓澹看來锐朴,要找的就是它,簡單的蔼囊,但也是管用的焚志。

繼續(xù)深究衣迷,JWT到底是怎樣和SpringSecurity結(jié)合的呢。下面上代碼酱酬,在上代碼前先說明一下壶谒,在本次實例中,涉及到兩個項目膳沽,一個項目是登錄的項目A佃迄,另一個項目是根據(jù)token進(jìn)行訪問的項目B。其中B項目沒有登錄贵少,也不會涉及登錄呵俏,只要有Token就可以訪問,Token失效了就訪問不了了滔灶。

A項目是登錄的項目普碎,也是一個只能通過登錄進(jìn)行訪問的后臺服務(wù)。B項目就是一個服務(wù)录平,只要用戶在A項目登錄了麻车,就可以訪問。


設(shè)計圖-1

A項目配置斗这,代碼如下

第一步动猬,A項目 POM.xml 引入文件
 <!-- spring-security 和 jwt 引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>            
            <groupId>io.jsonwebtoken</groupId>            
            <artifactId>jjwt</artifactId>            
            <version>0.9.0</version>        
        </dependency>
第二步,A項目SecurityConfig配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;

import com.example.demo.filter.JWTAuthenticationFilter;
import com.example.demo.filter.JWTLoginFilter;

/**
 * SpringSecurity的配置
 * 參考網(wǎng)址:https://blog.csdn.net/sxdtzhaoxinguo/article/details/77965226
 * @author 程就人生
 * @date 2019年5月26日
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserDetailsService myCustomUserService;

    @Autowired
    private MyPasswordEncoder myPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //關(guān)閉跨站請求防護(hù)
            .cors().and().csrf().disable()
            //允許不登陸就可以訪問的方法表箭,多個用逗號分隔
            .authorizeRequests().antMatchers("/test").permitAll()
            //其他的需要授權(quán)后訪問
            .anyRequest().authenticated()
            
            .and()         
            //增加登錄攔截
            .addFilter(new JWTLoginFilter(authenticationManager()))     
            //增加是否登陸過濾
            .addFilter(new JWTAuthenticationFilter(authenticationManager()))
            // 前后端分離是無狀態(tài)的赁咙,所以暫時不用session,將登陸信息保存在token中免钻。
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //覆蓋UserDetailsService類
        auth.userDetailsService(myCustomUserService)
        //覆蓋默認(rèn)的密碼驗證類
        .passwordEncoder(myPasswordEncoder);
    }
}
第三步彼水,實現(xiàn)配置文件中自定義的類
  1. MyPasswordEncoder類實現(xiàn)了默認(rèn)的PasswordEncoder 接口,可以對密碼加密和密碼對比進(jìn)行個性化定制
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

 /**
 * 自定義的密碼加密方法极舔,實現(xiàn)了PasswordEncoder接口
 * @author 程就人生
 * @date 2019年5月26日
 */
@Component
public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        //加密方法可以根據(jù)自己的需要修改
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return encode(charSequence).equals(s);
    }
}
  1. MyCustomUserService 實現(xiàn)了框架默認(rèn)的UserDetailsService凤覆,可以根據(jù)username從數(shù)據(jù)庫獲取用戶,查看用戶是否存在
/**
 * 登錄專用類,用戶登陸時拆魏,通過這里查詢數(shù)據(jù)庫
 * 自定義類盯桦,實現(xiàn)了UserDetailsService接口,用戶登錄時調(diào)用的第一類
 * @author 程就人生
 * @date 2019年5月26日
 */
@Component
public class MyCustomUserService implements UserDetailsService {

    /**
     * 登陸驗證時渤刃,通過username獲取用戶的所有權(quán)限信息
     * 并返回UserDetails放到spring的全局緩存SecurityContextHolder中拥峦,以供授權(quán)器使用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //在這里可以自己調(diào)用數(shù)據(jù)庫,對username進(jìn)行查詢溪掀,看看在數(shù)據(jù)庫中是否存在
        MyUserDetails myUserDetail = new MyUserDetails();
        myUserDetail.setUsername(username);
        myUserDetail.setPassword("123456");
        return myUserDetail;
    }
}
  1. MyUserDetails 實現(xiàn)了框架的UserDetails接口事镣,可以在該類中根據(jù)需要添加自己必需的屬性
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * 實現(xiàn)了UserDetails接口步鉴,只留必需的屬性揪胃,也可添加自己需要的屬性
 * @author 程就人生
 * @date 2019年5月26日
 */
public class MyUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    //登錄用戶名
    private String username;
    //登錄密碼
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

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

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

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

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

    @Override
    public String getUsername() {
        return this.username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}
  1. JWTLoginFilter 實現(xiàn)了框架自帶的UsernamePasswordAuthenticationFilter 接口璃哟,對攔截做處理,以便登錄成功后喊递,在頭部設(shè)置token返回随闪;不管登錄成功還是失敗,都有JSON數(shù)據(jù)返回
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.example.demo.entity.User;
import com.example.demo.security.MyUserDetails;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; 

/**
 * 驗證用戶名密碼正確后骚勘,生成一個token铐伴,放在header里,返回給客戶端 
 * @author 程就人生
 * @date 2019年5月26日
 */
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { 
    
    private AuthenticationManager authenticationManager;     
    
    public JWTLoginFilter(AuthenticationManager authenticationManager) { 
        
        this.authenticationManager = authenticationManager;    
        
    } 
    
    /**
     * 接收并解析用戶憑證俏讹,出現(xiàn)錯誤時当宴,返回json數(shù)據(jù)前端
     */
    @Override    
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res){        
        try {            
            User user =new ObjectMapper().readValue(req.getInputStream(), User.class);             
            return authenticationManager.authenticate(                    
                    new UsernamePasswordAuthenticationToken(                            
                            user.getUsername(),                            
                            user.getPassword(),                            
                            new ArrayList<>())            
                    );        
            } catch (Exception e) {
                try {
                    //未登錄出現(xiàn)賬號或密碼錯誤時,使用json進(jìn)行提示
                    res.setContentType("application/json;charset=utf-8");
                    res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    PrintWriter out = res.getWriter();                  
                    Map<String,Object> map = new HashMap<String,Object>();
                    map.put("code",HttpServletResponse.SC_UNAUTHORIZED);
                    map.put("message","賬號或密碼錯誤泽疆!");
                    out.write(new ObjectMapper().writeValueAsString(map));
                    out.flush();
                    out.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
                throw new RuntimeException(e);        
            }    
    }
    
    /**
     * 用戶登錄成功后户矢,生成token,并且返回json數(shù)據(jù)給前端
     */
    @Override    
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,FilterChain chain, Authentication auth){         
        
        //json web token構(gòu)建
        String token = Jwts.builder()    
                //此處為自定義的、實現(xiàn)org.springframework.security.core.userdetails.UserDetails的類殉疼,需要和配置中設(shè)置的保持一致
                //此處的subject可以用一個用戶名梯浪,也可以是多個信息的組合,根據(jù)需要來定
                .setSubject(((MyUserDetails) auth.getPrincipal()).getUsername())    
                //設(shè)置token過期時間瓢娜,24小時
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000)) 
                
                //設(shè)置token簽名挂洛、密鑰
                .signWith(SignatureAlgorithm.HS512, "MyJwtSecret")       
                
                .compact();  
        
        //返回token
        res.addHeader("Authorization", "Bearer " + token); 
        
        try {
            //登錄成功時,返回json格式進(jìn)行提示
            res.setContentType("application/json;charset=utf-8");
            res.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = res.getWriter();                  
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("code",HttpServletResponse.SC_OK);
            map.put("message","登陸成功眠砾!");
            out.write(new ObjectMapper().writeValueAsString(map));
            out.flush();
            out.close();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
}
  1. JWTAuthenticationFilter 類實現(xiàn)了BasicAuthenticationFilter 接口虏劲,對Controller中需要登錄后才能訪問的方法進(jìn)行了攔截,沒有登錄褒颈,則不能訪問伙单,返回JSON信息進(jìn)行提示
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;

/**
 * 是否登陸驗證方法
 * @author 程就人生
 * @date 2019年5月26日
 */
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
    
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 對請求進(jìn)行過濾
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {        
        try {
            //請求體的頭中是否包含Authorization
            String header = request.getHeader("Authorization");     
            //Authorization中是否包含Bearer,有一個不包含時直接返回
            if (header == null || !header.startsWith("Bearer ")) {
                chain.doFilter(request, response);
                responseJson(response);
                return;        
            } 
            //獲取權(quán)限失敗哈肖,會拋出異常
            UsernamePasswordAuthenticationToken authentication = getAuthentication(request); 
            //獲取后吻育,將Authentication寫入SecurityContextHolder中供后序使用
            SecurityContextHolder.getContext().setAuthentication(authentication); 
            chain.doFilter(request, response);
        } catch (Exception e) {
            responseJson(response);
            e.printStackTrace();
        }     
    }

    /**
     * 未登錄時的提示
     * @param response
     */
    private void responseJson(HttpServletResponse response){
        try {
            //未登錄時,使用json進(jìn)行提示
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();                 
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("code",HttpServletResponse.SC_FORBIDDEN);
            map.put("message","請登錄淤井!");
            out.write(new ObjectMapper().writeValueAsString(map));
            out.flush();
            out.close();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
    
    /**
     * 通過token布疼,獲取用戶信息
     * @param request
     * @return
     */
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {        
        String token = request.getHeader("Authorization");        
        if (token != null) {            
            //通過token解析出用戶信息            
            String user = Jwts.parser()   
                    //簽名、密鑰
                    .setSigningKey("MyJwtSecret")                    
                    .parseClaimsJws(token.replace("Bearer ", ""))                    
                    .getBody()                    
                    .getSubject();     
            //不為null币狠,返回
            if (user != null) {                
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());            
            }           
            return null;        
        }        
        return null;    
    } 
        
}
  1. 在登錄過濾器中接收參數(shù)的實體類游两,也可以直接接收,這一個類不是必須的
public class User {
    
    private long id;
    private String username;
    private String password;

    public long getId() {
        return id;
    }

    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;
    }
}

B項目的配置

第一步漩绵,在pom中引入必須的架包
<!-- spring-security 和 jwt 引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>            
            <groupId>io.jsonwebtoken</groupId>            
            <artifactId>jjwt</artifactId>            
            <version>0.9.0</version>        
        </dependency>
第二步贱案,增加SecurityConfig配置文件
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

import com.example.demo.filter.JWTAuthenticationFilter;

/**
 * SpringSecurity的配置
 * 參考網(wǎng)址:https://blog.csdn.net/sxdtzhaoxinguo/article/details/77965226
 * @author 程就人生
 * @date 2019年5月26日
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //關(guān)閉跨站請求防護(hù)
            .cors().and().csrf().disable()
            //允許不登陸就可以訪問的方法,多個用逗號分隔
            .authorizeRequests()
            //其他的需要授權(quán)后訪問
            .anyRequest().authenticated()
            
            .and()
            //增加是否登陸過濾
            .addFilter(new JWTAuthenticationFilter(authenticationManager()))
            // 前后端分離是無狀態(tài)的止吐,所以暫時不用session宝踪,將登陸信息保存在token中侨糟。
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }
    
}
第三步,在增加對方法是否登錄進(jìn)行攔截的過濾器
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;

/**
 * 是否登陸驗證方法
 * @author 程就人生
 * @date 2019年5月26日
 */
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
    
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 對請求進(jìn)行過濾
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {        
        try {
            //請求體的頭中是否包含Authorization
            String header = request.getHeader("Authorization");     
            //Authorization中是否包含Bearer瘩燥,有一個不包含時直接返回
            if (header == null || !header.startsWith("Bearer ")) {
                chain.doFilter(request, response);
                responseJson(response);
                return;        
            } 
            //獲取權(quán)限失敗秕重,會拋出異常
            UsernamePasswordAuthenticationToken authentication = getAuthentication(request); 
            //獲取后,將Authentication寫入SecurityContextHolder中供后序使用
            SecurityContextHolder.getContext().setAuthentication(authentication); 
            chain.doFilter(request, response);
        } catch (Exception e) {
            responseJson(response);
            e.printStackTrace();
        }     
    }

    /**
     * 未登錄時的提示
     * @param response
     */
    private void responseJson(HttpServletResponse response){
        try {
            //未登錄時厉膀,使用json進(jìn)行提示
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();                 
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("code",HttpServletResponse.SC_FORBIDDEN);
            map.put("message","請登錄溶耘!");
            out.write(new ObjectMapper().writeValueAsString(map));
            out.flush();
            out.close();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
    
    /**
     * 通過token,獲取用戶信息
     * @param request
     * @return
     */
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {        
        String token = request.getHeader("Authorization");        
        if (token != null) {            
            //通過token解析出用戶信息            
            String user = Jwts.parser()   
                    //簽名鹽
                    .setSigningKey("MyJwtSecret")                    
                    .parseClaimsJws(token.replace("Bearer ", ""))                    
                    .getBody()                    
                    .getSubject();     
            //不為null服鹅,返回
            if (user != null) {                
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());            
            }           
            return null;        
        }        
        return null;    
    } 
        
}

從B項目的配置中凳兵,可以看出,B項目配置的太簡潔了企软,只需要攔截一下沒有登錄的請求留荔,連登錄也都省了。

A和B項目中分別添加一個Controller澜倦,用于測試

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 測試用例
 * @author 程就人生
 * @date 2019年5月26日
 */
@RestController
public class IndexController {

    @GetMapping("/index")
    public Object index(){
        
        return "index";
    }
}

使用測試工具進(jìn)行測試

第一步聚蝶,測試A項目和B項目的index是否能訪問,結(jié)果都不能訪問藻治,測試結(jié)果OK
測試結(jié)果-1

測試結(jié)果-2
第二步碘勉,通過登錄獲取token,登錄成功后桩卵,返回了JSON格式的提示验靡,返回的token在頭部,點擊響應(yīng)頭雏节,獲取token
測試結(jié)果-3

測試結(jié)果-4
第三步胜嗓,將token拷貝至A項目index的頭部,B項目index的頭部钩乍,測試結(jié)果ok辞州,都可以訪問,也可以把token時間設(shè)置的短一些寥粹,測試一下token過期了变过,是否還能訪問。
測試結(jié)果-5

測試結(jié)果-6

最后涝涤,感覺一下Token的結(jié)構(gòu)媚狰,去掉前面固定的Bearer ,后面的分成三個部分阔拳,中間用點隔開崭孤,這個就簡單了解下吧。

  • Header(頭部)
  • Payload(負(fù)載)
  • Signature(簽名)
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZW5nIiwiZXhwIjoxNTU4OTUxMjM5fQ.X7lOhHJljxnVcNEckYSX22rgTDN0ToRJLaPb_1dAoPzx6q_eN5B5iOxO2GXoNUllIfQG6SrdJhgYzKZPTMsDIg

Spring Security整合JWT,實現(xiàn)單點登錄的功能辨宠,到此就告一段落了遗锣,看起來是不是很簡單呢,那就動手試一試吧彭羹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黄伊,一起剝皮案震驚了整個濱河市泪酱,隨后出現(xiàn)的幾起案子派殷,更是在濱河造成了極大的恐慌,老刑警劉巖墓阀,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毡惜,死亡現(xiàn)場離奇詭異,居然都是意外死亡斯撮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臼节,“玉大人皆警,你說我怎么就攤上這事∫缡” “怎么了垮刹?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長张弛。 經(jīng)常有香客問我荒典,道長,這世上最難降的妖魔是什么吞鸭? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任寺董,我火速辦了婚禮,結(jié)果婚禮上刻剥,老公的妹妹穿的比我還像新娘遮咖。我一直安慰自己,他們只是感情好造虏,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布盯滚。 她就那樣靜靜地躺著,像睡著了一般酗电。 火紅的嫁衣襯著肌膚如雪魄藕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天撵术,我揣著相機(jī)與錄音背率,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛寝姿,可吹牛的內(nèi)容都是我干的交排。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼饵筑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了根资?” 一聲冷哼從身側(cè)響起架专,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玄帕,沒想到半個月后部脚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡裤纹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年委刘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹰椒。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡锡移,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漆际,到底是詐尸還是另有隱情淆珊,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布灿椅,位于F島的核電站套蒂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏茫蛹。R本人自食惡果不足惜操刀,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婴洼。 院中可真熱鬧骨坑,春花似錦、人聲如沸柬采。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粉捻。三九已至礁遣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肩刃,已是汗流浹背祟霍。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工杏头, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沸呐。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓醇王,卻偏偏與公主長得像,于是被迫代替她去往敵國和親崭添。 傳聞我的和親對象是個殘疾皇子寓娩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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