technology-integration(七)---使用SpringSecurity做JWT認(rèn)證授權(quán)

SpringSecurity是什么

Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應(yīng)用上下文中配置的Bean颜懊,充分利用了Spring IoC财岔,DI(控制反轉(zhuǎn)Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,為應(yīng)用系統(tǒng)提供聲明式的安全訪問控制功能河爹,減少了為企業(yè)系統(tǒng)安全控制編寫大量重復(fù)代碼的工作匠璧。(引用百度百科)

為什么使用SpringSecurity

可以說SpringBoot的快速發(fā)展也使得了SpringSecurity的熱度往上漲,在SpringBoot以前咸这,Shiro和SpringSecurity的框架都是主流的安全框架夷恍,但Security的配置非常臃腫,性能也相對(duì)慢一些媳维,最大的缺點(diǎn)還是必須依賴于Spring框架才能開發(fā)酿雪,不過唯一值得一提的就是Security默認(rèn)實(shí)現(xiàn)了更多功能,更是提供了oauth授權(quán)的實(shí)現(xiàn)侄刽。不過一切的改變還是源于SpringBoot的出現(xiàn)指黎,SpringBoot整合SpringSecurity的步驟非常簡(jiǎn)單,只需要繼承WebSecurityConfigurerAdapter這個(gè)類并實(shí)現(xiàn)認(rèn)證方法州丹,接著配置一下登錄的uri即可完成一個(gè)簡(jiǎn)單的用戶認(rèn)證功能醋安,可以說整個(gè)功能都不用5分鐘就能實(shí)現(xiàn)。我個(gè)人還是比較喜歡SpringBoot全家桶墓毒,不需要解決框架整合上的小麻煩吓揪,功能上來說也很強(qiáng)大。

JWT

JWT是一種用于雙方之間傳遞安全信息的簡(jiǎn)潔的所计、URL安全的表述性聲明規(guī)范磺芭。JWT作為一個(gè)開放的標(biāo)準(zhǔn)(RFC 7519),定義了一種簡(jiǎn)潔的醉箕,自包含的方法用于通信雙方之間以Json對(duì)象的形式安全的傳遞信息钾腺。因?yàn)閿?shù)字簽名的存在徙垫,這些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘鑰對(duì)進(jìn)行簽名放棒。簡(jiǎn)潔(Compact): 可以通過URL姻报,POST參數(shù)或者在HTTP header發(fā)送,因?yàn)閿?shù)據(jù)量小间螟,傳輸速度也很快 自包含(Self-contained):負(fù)載中包含了所有用戶所需要的信息吴旋,避免了多次查詢數(shù)據(jù)庫

怎么使用

使用JWT,我們只需要在請(qǐng)求的請(qǐng)求頭上添加如圖下類似的數(shù)據(jù)(token)厢破。后端根據(jù)需要認(rèn)證的url進(jìn)行攔截荣瑟,取出Hearders里面的數(shù)據(jù),緊接著解析出這段token的包含的信息摩泪,判斷信息是否正確即可笆焰。token其實(shí)就是根據(jù)信息加密而來的一段字符串,我們將需要用到的信息放到token中见坑,token包含的信息盡可能的簡(jiǎn)潔嚷掠。


image.png

注意:

雖然簡(jiǎn)單的jwt認(rèn)證并沒有什么難度,但如果你沒使用過SpringSecurity荞驴,建議還是先去簡(jiǎn)單的學(xué)習(xí)一下不皆。


開始

  1. 編寫通過用戶id或用戶手機(jī)號(hào)碼查詢User和Role的方法
  2. 編寫Token生成工具類
  3. 繼承UserDetails接口
  4. 繼承UserDetailsService接口,實(shí)現(xiàn)用戶認(rèn)證方法
  5. 編寫用戶賬號(hào)驗(yàn)證失敗處理器與權(quán)限不足處理器
  6. 編寫Token驗(yàn)證過濾器
  7. 配置SpringSecurity Config
  8. 實(shí)現(xiàn)登錄方法

整個(gè)流程還是相對(duì)完善的熊楼,所以步驟稍多

導(dǎo)入jar

這里需要提一下的就是霹娄,當(dāng)你引入這個(gè)包的時(shí)候,SpringBoot默認(rèn)會(huì)為項(xiàng)目所有的請(qǐng)求添加認(rèn)證鲫骗,這也是SpringSecurity的常規(guī)操作犬耻,如果你還不知道的話,趕快剎車調(diào)頭回家補(bǔ)課挎峦。

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

實(shí)現(xiàn)用戶登錄方法

用戶通過手機(jī)號(hào)及密碼進(jìn)行登錄,我們需要先獲取用戶的身份信息以及角色信息

UserMapper.xml
  <resultMap id="User_Role" type="com.viu.technology.po.User">
    <id property="id" column="id" javaType="java.lang.String" jdbcType="BIGINT" />
    <result property="name" column="name" javaType="java.lang.String" jdbcType="VARCHAR" />
    <result property="phone" column="phone" javaType="java.lang.String" jdbcType="VARCHAR" />
    <result property="password" column="password" javaType="java.lang.String" jdbcType="VARCHAR" />
    <collection property="roles" ofType="com.viu.technology.po.Role">
      <id property="id" column="role_id" jdbcType="BIGINT"/>
      <result property="roleName" column="role_name" jdbcType="VARCHAR" />
    </collection>
  </resultMap>

  <select id="selUserAndRoleByPhone" parameterType="java.lang.String" resultMap="User_Role">
    SELECT u.id,u.name,u.password,u.phone,r.id as role_id ,r.role_name
    from t_user u
    LEFT JOIN t_role r on u.id=r.user_id
    where u.phone=#{phone,jdbcType=VARCHAR}
  </select>

  <select id="selUserAndRoleById" parameterType="java.lang.String" resultMap="User_Role">
    SELECT u.id,u.name,u.password,u.phone,r.id as role_id ,r.role_name
    from t_user u
    LEFT JOIN t_role r on u.id=r.user_id
    where u.id=#{id,jdbcType=VARCHAR}
  </select>
UserMapper.java
    User selUserAndRoleByPhone(String phone);

    User selUserAndRoleById(String id);
UserDao.java
    User selUserAndRoleByPhone(String phone);

    User selUserAndRoleById(String id);
UserDaoImpl.java
    public User selUserAndRoleByPhone(String phone) {
        User user = userMapper.selUserAndRoleByPhone(phone);
        return user;
    }


    public User selUserAndRoleById(String id){
        User user = userMapper.selUserAndRoleById(id);
        return user;
    }
UserService.java
    User getUserAndRoleByPhone(String phone);

    User getUserAndRoleById(String id);
UserServiceImpl.java
    public User getUserAndRoleByPhone(String phone) {
        User user = userDao.selUserAndRoleByPhone(phone);
        return user;
    }

    public User getUserAndRoleById(String id) {
        User user = userDao.selUserAndRoleById(id);
        return user;
    }

操作數(shù)據(jù)庫獲取用戶身份信息的代碼就到此為止了合瓢,接下來就開始編寫SpringSecurity+jwt的認(rèn)證代碼了


編寫Token生成工具類----JwtTokenUtil

工具類主要用作生成token坦胶、刷新token以及驗(yàn)證token。Token和Session一個(gè)很大的區(qū)別就是無登錄狀態(tài)晴楔,我們可以利用清除session做登出的操作顿苇,但無法利用token直接做登出操作,后續(xù)會(huì)進(jìn)行講解税弃。
這個(gè)token里的信息比較簡(jiǎn)單纪岁,只存放了sub和create,你可以根據(jù)自己業(yè)務(wù)需求在generateToken(UserDetails userDetails)方法里面添加不同的數(shù)據(jù)即可则果,后續(xù)通過getClaimsFromToken方法獲取Claims對(duì)象幔翰,接著調(diào)用Claims對(duì)象的get方法獲取出對(duì)應(yīng)的數(shù)據(jù)即可漩氨。

@Component
public class JwtTokenUtil{

    /**
     * 密鑰
     */
    private static final String secret = "lkhouhubkljgpihojblkjboiboihu9u";

    /**
     * 從數(shù)據(jù)聲明生成令牌
     *
     * @param claims 數(shù)據(jù)聲明
     * @return 令牌
     */
    public static String generateToken(Map<String, Object> claims) {
        //設(shè)置token的有效期為24*7小時(shí),也就是一周
        Date expirationDate = new Date(System.currentTimeMillis() +60*60*24*7 * 1000);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 從令牌中獲取數(shù)據(jù)聲明
     *
     * @param token 令牌
     * @return 數(shù)據(jù)聲明
     */
    public static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 生成令牌
     *
     * @param userDetails 用戶
     * @return 令牌
     */
    public static String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub", userDetails.getUsername());
        claims.put("created", new Date());
        return generateToken(claims);
    }

    /**
     * 從令牌中獲取用戶名
     *
     * @param token 令牌
     * @return 用戶名
     */
    public static String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判斷令牌是否過期
     *
     * @param token 令牌
     * @return 是否過期
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public static String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 驗(yàn)證令牌
     *
     * @param token       令牌
     * @param userDetails 用戶
     * @return 是否有效
     */
    public static Boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

繼承UserDetails接口

UserDetails接口是SpringSecurity框架用于認(rèn)證授權(quán)的一個(gè)載體,只有實(shí)現(xiàn)了這個(gè)接口的類才能被SpringSecurity驗(yàn)證遗增,

public class User implements UserDetails {
    private String id;

    private String name;

    private String password;

    private String phone;

    private List<Role> roles;


    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public User(String id, String name, String password, String phone) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.phone = phone;
    }

    public User() {
        super();
    }

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //獲取用戶角色權(quán)限叫惊,此處從數(shù)據(jù)庫表Role中獲取
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> auths = new ArrayList<>();
        List<Role> roles = getRoles();
        if (roles!=null) {
            for (Role role : roles) {
                auths.add(new SimpleGrantedAuthority(role.getRoleName()));
            }
        }
        return auths;
    }

    //這個(gè)是UserDetails默認(rèn)實(shí)現(xiàn)獲取密碼的方法
    @Override
    public String getPassword() {
        return password;
    }


    //這里getUsername翻譯過來就是獲取用戶名的意思,但這個(gè)可以作為我們獲取用戶信息的一個(gè)標(biāo)識(shí)
    @Override
    public String getUsername() {
        return id;
    }

    //用戶賬號(hào)是否過期做修,暫時(shí)沒這個(gè)功能霍狰,默認(rèn)返回true,即未過期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //用戶賬號(hào)是否鎖定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //用戶憑證是否過期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //賬號(hào)是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }

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

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

編寫登錄認(rèn)證方法JwtUserDetailsServiceImpl.java

該類位于com.viu.technology.service.auth包下(自行建包)
JwtUserDetailsServiceImpl實(shí)現(xiàn)了UserDetailsService接口饰及,SpringSecurity會(huì)去IOC容器中尋找實(shí)現(xiàn)這個(gè)接口的實(shí)現(xiàn)類蔗坯,并將該實(shí)現(xiàn)類作為默認(rèn)的認(rèn)證類。這個(gè)類主要用于獲取用戶身份信息燎含,并不需要我們?nèi)ヅ袛嘤脩裘兔艽a是否匹配宾濒。參照UserDetails實(shí)現(xiàn)的getPassword和getUsername方法。

這里之所要對(duì)username的長(zhǎng)度進(jìn)行判斷是因?yàn)樘闭颍覀兊卿浀臅r(shí)候用的是手機(jī)號(hào)+明文密碼進(jìn)行登錄仓坞,而保存在token里的信息只有id。登錄方法和Token認(rèn)證過濾器都會(huì)調(diào)用loadUserByUsername方法潦匈,所以需要做一個(gè)判斷烘嘱。可能會(huì)有一點(diǎn)疑問尚粘,既然是這樣择卦,為什么不直接用手機(jī)號(hào)做為token的傳遞信息就好了呢,主要還是因?yàn)槲覀兪褂檬謾C(jī)號(hào)查詢的情況比較少郎嫁,而表的主鍵id才是經(jīng)常用的秉继。

@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    @Lazy
    private UserService userService;

    public JwtUserDetailsServiceImpl(){
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = null;
        if (username.length() == 32) {
            user= userService.getUserAndRoleById(username);
        } else if(username.length()==11) {
            user= userService.getUserAndRoleByPhone(username);
        }

        log.info("user:" + user);

        if (user == null) {
            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
        }else{
            return user;
        }
    }
}

編寫賬號(hào)密碼驗(yàn)證失敗處理器EntryPointUnauthorizedHandler.java

位于com.viu.technology.handler包下,自行創(chuàng)建

@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {

    private static Logger log = LoggerFactory.getLogger(EntryPointUnauthorizedHandler.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setStatus(401);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(JsonUtil.objectToString(Result.fail(ResultCode.USER_LOGIN_FIAL)));

    }

}

編寫賬戶權(quán)限不足處理器RestAccessDeniedHandler.java

位于com.viu.technology.handler包下泽铛,自行創(chuàng)建

@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setStatus(403);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(JsonUtil.objectToString(Result.fail(ResultCode.USER_PERMISSION_DENIED)));
    }
}

編寫Token驗(yàn)證過濾器JwtAuthenticationTokenFilter.java

位于com.viu.technology.filter包下尚辑,自行創(chuàng)建

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private static Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Autowired
    private UserDetailsService userDetailsService;

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

        String authHeader = request.getHeader("Authorization");
        //該字符串作為Authorization請(qǐng)求頭的值的前綴
        String tokenHead = "tech-";
        if (authHeader != null && authHeader.startsWith(tokenHead)) {
            String authToken = authHeader.substring(tokenHead.length());
            //從token中獲取userId
            String userId = JwtTokenUtil.getUsernameFromToken(authToken);
            if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                //調(diào)用UserDetailsService的認(rèn)證方法(JwtUserDetailsServiceImpl實(shí)現(xiàn)類)
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(userId);
                //驗(yàn)證token是否正確
                if (JwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    //將獲取到的用戶身份信息放到SecurityContextHolder中,這個(gè)類是為了在線程中保存當(dāng)前用戶的身份信息
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        } else {
            log.info("沒有獲取到token");
        }
        chain.doFilter(request, response);
    }



}

配置SpringSecurity

位于com.viu.technology.config.security包下盔腔,自行創(chuàng)建

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//開啟security方法級(jí)別權(quán)限控制注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Autowired
    private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
    @Autowired
    private RestAccessDeniedHandler restAccessDeniedHandler;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private SimpleUrlAuthenticationSuccessHandler successHandler;

    @Autowired
    private SimpleUrlAuthenticationFailureHandler failureHandler;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()
                //這里的參數(shù)為不需要認(rèn)證的uri,**代表匹配多級(jí)路徑杠茬,*代表匹配一級(jí)路徑,#代表一個(gè)字符....
                .antMatchers(
                        "/demo/**",
                        "/user/generate/token"
                ).permitAll()
                //這里表示該路徑需要管理員角色
                .antMatchers("/auth/test").hasAnyAuthority("管理員")
                .anyRequest().authenticated()
                .and()
                .headers().cacheControl();


        //添加認(rèn)證過濾
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //添加權(quán)限不足及驗(yàn)證失敗處理器
        httpSecurity.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler);

    }


    //這個(gè)為SpringSecurity的加密類
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

實(shí)現(xiàn)登錄方法

UserService.java
    String login(String phone, String password);
UserServiceImpl.java

這里需要注意一下弛随,UsernamePasswordAuthenticationToken會(huì)自動(dòng)將password進(jìn)行加密之后再比對(duì)瓢喉,而我們之前寫的注冊(cè)用戶方法是以明文方式存入數(shù)據(jù)庫的,并沒有加密舀透,所以我們需要修改一下用戶注冊(cè)方法栓票,然后重新注冊(cè)

    public String login(String phone, String password) {
        //將用戶名和密碼生成Token
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(phone, password);
        //調(diào)用該方法時(shí)SpringSecurity會(huì)去調(diào)用JwtUserDetailsServiceImpl 進(jìn)行驗(yàn)證
        Authentication authentication = authenticationManager.authenticate(upToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return JwtTokenUtil.generateToken(userDetails);
    }



    @Autowired
    PasswordEncoder passwordEncoder;

    public User registerUser(User user) {
        //在插入數(shù)據(jù)庫時(shí)將原密碼進(jìn)行加密
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        User userRes = userDao.insertUser(user);
        Role roleRes = roleDao.insertRole(new Role("普通群眾", user.getId()));
        List list = new ArrayList();
        list.add(roleRes);

        if (null != userRes && null != roleRes) {
            userRes.setRoles(list);
            return user;
        }
        return null;
    }
UserController.java
    @PostMapping(value = "/generate/token")
    public Result getToken(String phone, String password) throws AuthenticationException {

        String token = userService.login(phone, password);
        return Result.success(token);
    }

測(cè)試獲取token接口

獲取token

接著我們調(diào)用一下之前寫的注冊(cè)接口,發(fā)現(xiàn)沒發(fā)注冊(cè)愕够,因?yàn)槲覀冊(cè)赟pringSecurity的配置中并沒有開放這個(gè)接口的認(rèn)證走贪,自行添加佛猛。注冊(cè)是不需要用戶身份驗(yàn)證的,否則你讓人家怎么注冊(cè)嘛厉斟。挚躯。。


image.png

測(cè)試Token是否能正常使用

UserController.java
    @GetMapping("/self/info")
    public Result getUserSelfInfo() {
       //由于通過驗(yàn)證后我們會(huì)把用戶對(duì)象存到SecurityContextHolder中擦秽,所以這時(shí)候我們能通過下面這句代碼獲取到用戶的身份信息
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return Result.success(user);
    }

接下來測(cè)試一下码荔,如果能夠正常獲取就代表成功,記住token前面要加tech-這個(gè)幾個(gè)字符串感挥,看不順眼的話自己去改過濾器



溫馨提醒

你們會(huì)發(fā)現(xiàn)讀出來的數(shù)據(jù)和我稍微有點(diǎn)不一樣對(duì)吧缩搅,哈哈哈哈,肯定啊触幼,你們沒有過濾一下敏感字段(密碼我忘了過濾了0.0)硼瓣,在User類上加入@JSONField(serialize = false)注解即可,SpringBoot會(huì)將持有該注解的字段過濾不進(jìn)行輸出置谦。


image.png


更多文章請(qǐng)關(guān)注該 technology-integration全面解析專題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堂鲤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子媒峡,更是在濱河造成了極大的恐慌瘟栖,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谅阿,死亡現(xiàn)場(chǎng)離奇詭異半哟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)签餐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門寓涨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人氯檐,你說我怎么就攤上這事戒良。” “怎么了冠摄?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵糯崎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我耗拓,道長(zhǎng)拇颅,這世上最難降的妖魔是什么奏司? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任乔询,我火速辦了婚禮,結(jié)果婚禮上韵洋,老公的妹妹穿的比我還像新娘竿刁。我一直安慰自己黄锤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布食拜。 她就那樣靜靜地躺著鸵熟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪负甸。 梳的紋絲不亂的頭發(fā)上流强,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音呻待,去河邊找鬼打月。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蚕捉,可吹牛的內(nèi)容都是我干的奏篙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼迫淹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼秘通!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敛熬,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤肺稀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后荸型,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盹靴,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年瑞妇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稿静。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辕狰,死狀恐怖改备,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蔓倍,我是刑警寧澤悬钳,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站偶翅,受9級(jí)特大地震影響默勾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聚谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一母剥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦环疼、人聲如沸习霹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淋叶。三九已至,卻和暖如春伪阶,著一層夾襖步出監(jiān)牢的瞬間煞檩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工栅贴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留形娇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓筹误,卻偏偏與公主長(zhǎng)得像桐早,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子厨剪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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