Spring Boot之整合Spring Security: 訪問認(rèn)證

前言

在過往的一些Spring Boot學(xué)習(xí)項(xiàng)目中饺饭,我們會發(fā)現(xiàn)渤早,我們開發(fā)的API都不需要認(rèn)證职车,對所有人開放,連登錄都不需要鹊杖,毫無安全可言悴灵。
在項(xiàng)目實(shí)戰(zhàn)中往往需要做好認(rèn)證、授權(quán)骂蓖、攻擊防護(hù)积瞒,Spring Boot在這方面也提供了快速解決方案,即:推薦使用Spring Security登下。

  • Spring Boot為Spring Security提供了自動化配置方案茫孔,可零配置使用 Spring Security。

項(xiàng)目代碼已上傳Git Hub被芳,歡迎取閱:

簡單入門

1. 添加依賴缰贝;

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

2. 編寫Controller;

package com.github.dylanz666.controller;

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

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String sayHello() throws Exception {
        return "Hello!";
    }
}

3. 啟動項(xiàng)目并訪問API畔濒;

  • 啟動項(xiàng)目:

注意一條log:Using generated security password: e10ac5ca-d3ab-4f0e-8e25-cbcf6afce611剩晴,下文會使用到。

啟動項(xiàng)目
  • 在瀏覽器中訪問API:

API如:http://127.0.0.1:8080/hello

默認(rèn)登錄頁面
訪問API
  • 輸入用戶名密碼登錄:
    1). Username: 默認(rèn)用戶名為user;
    2). Password: 默認(rèn)密碼為log中打印的密碼侵状,e10ac5ca-d3ab-4f0e-8e25-cbcf6afce611赞弥;
    戶名密碼登錄
我們一起來分析一下:

1). 未登錄時訪問API會重定向到登錄頁面:http://127.0.0.1:8080/login
2). Spring Security為我們提供了默認(rèn)的登錄頁面趣兄,登錄頁面還算美觀绽左;
3). 登錄后,后續(xù)的請求中艇潭,會在請求頭中帶上含有JESSIONID的Cookie拼窥;

Cookie

可在項(xiàng)目application.properties中提前配置好用戶名和密碼,如:

server.port=8080
spring.security.user.name=dylanz
spring.security.user.password=666
用戶名密碼登錄

至此暴区,我們就實(shí)現(xiàn)了最簡單的登錄認(rèn)證闯团。


自定義登錄頁面實(shí)例

  • 未登錄狀態(tài)下API請求重定向到登錄頁面還是比較奇怪的,一般來說仙粱,API未登錄狀態(tài)下的請求應(yīng)該顯示狀態(tài)碼:401房交;
  • 通常情況下,應(yīng)該是進(jìn)入某個有訪問限制的頁面伐割,當(dāng)未登錄時候味,重定向到登錄頁面刃唤;

因此,我們將場景變?yōu)椋?/h4>

我們將采用視圖技術(shù)尚胞,簡單做個案例。Spring Boot框架內(nèi)使用視圖技術(shù)可參考:

thymeleaf使用準(zhǔn)備:

1). 添加thymeleaf依賴笼裳;
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2). 修改配置文件;
server.port=8080
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8

1. 定義主頁home.html粱玲;

  • 在resources下創(chuàng)建templates文件夾躬柬,并創(chuàng)建home.html文件:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>

<p>Click <a th:href="@{/hello.html}">here</a> to see a greeting.</p>
</body>
</html>
  • 前往hello.html頁面的代碼:<a th:href="@{/hello.html}">here</a>

2. 定義hello.html頁面;

  • 在templates文件夾下創(chuàng)建hello.html文件:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="Sign Out"/>
</form>
</body>
</html>
  • hello.html頁面上提供一個登出入口"Sign Out";

3. 自定義login/logout頁面抽减;

  • 在templates文件夾下創(chuàng)建login.html文件:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    You have been logged out.
</div>
<form th:action="@{/login}" method="post">
    <div><label th:style="'background:red;'"> User Name: <input type="text" name="username"/> </label></div>
    <div><label th:style="'background:red;'"> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
  • 當(dāng)用戶名密碼錯誤時提示信息:Invalid username and password.
  • 當(dāng)?shù)浅鰰r提示信息:You have been logged out.
  • 為了演示自定義頁面允青,我還特地改了下頁面元素樣式,把User Name和Password label的背景色改為紅色:th:style="'background:red;'"
    (筆者沒有花過多的時間處理樣式哈卵沉,此處只做簡單演示)

4. 組織頁面行為颠锉;

1). 配置模板匹配規(guī)則;

目的是使網(wǎng)站的url指向具體視圖史汗,而不是當(dāng)作API來訪問琼掠;
在項(xiàng)目下創(chuàng)建config包,并在config包內(nèi)創(chuàng)建WebMvcConfig類淹办,編寫WebMvcConfig類如下:

package com.github.dylanz666.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home.html").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello.html").setViewName("hello");
        registry.addViewController("/login.html").setViewName("login");
    }
}
  • 訪問/和/home.html路徑時眉枕,使用模板:home.html
  • 訪問/hello.html路徑時怜森,使用模板:hello.html速挑;
  • 訪問/login.html路徑時,使用模板:login.html副硅。
2). 頁面訪問權(quán)限設(shè)置姥宝;

在config包下創(chuàng)建類:WebSecurityConfig,編寫類如下:

package com.github.dylanz666.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/", "/home.html").permitAll()//這2個url不用訪問認(rèn)證
                .anyRequest().authenticated()//其他url都需要訪問認(rèn)證
                .and()
                .formLogin()
                .loginPage("/login.html")//登錄頁面的url
                .loginProcessingUrl("/login")//登錄表使用的API
                .permitAll()//login.html和login不需要訪問認(rèn)證
                .and()
                .logout()
                .permitAll();//logout不需要訪問認(rèn)證
    }
}

幾點(diǎn)解釋:

  • @EnableWebSecurity:官網(wǎng)說這是為了開啟Web Security支持恐疲,并提供Spring MVC集成腊满,具體咋回事咱也不知道呀,跟著用就是對了培己!
  • .antMatchers("/", "/home.html").permitAll():配置不需要認(rèn)證的url碳蛋,也即任何人都可以訪問的url;
  • .loginPage("/login.html"):配置登錄頁面的url省咨,由于我們自定義了登錄頁面肃弟,因此需使用這個配置,如果不是用此配置,則使用Spring Security提供的默認(rèn)登錄頁面笤受;
  • .loginProcessingUrl("/login"): 配置登錄表單使用的API穷缤,Spring Security默認(rèn)提供"/login"接口,用于登錄驗(yàn)證箩兽;

3). 啟動項(xiàng)目查看效果津肛;

  • 訪問主頁:http://127.0.0.1:8080/

訪問主頁
  • 點(diǎn)擊頁面中的"here"鏈接;

點(diǎn)擊鏈接

此時嘗試訪問http://127.0.0.1:8080/hello.html汗贫,但由于我們沒有登錄身坐,因此Spring Security自動幫我們跳轉(zhuǎn)到登錄頁面:http://127.0.0.1:8080/login.html

  • 登錄;

登錄

登錄后
  • 登錄后訪問項(xiàng)目寫好的API芳绩;

筆者在項(xiàng)目中的controller包中寫了個HelloController類掀亥,類中寫了個get類型的API,代碼如下:

package com.github.dylanz666.controller;

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

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String sayHello() throws Exception {
        return "Hello!";
    }
}

此時在瀏覽器中直接訪問API:http://127.0.0.1:8080/hello

登錄訪問API

  • 登出妥色;

點(diǎn)擊hello.html頁面上的"Sign Out"按鈕登出;


登出

此時退出到登錄頁面遏片,且頁面有提示信息:You have been logged out.

  • 登出后訪問項(xiàng)目寫好的API嘹害;

再次在瀏覽器中直接訪問API:http://127.0.0.1:8080/hello
此時我們會發(fā)現(xiàn)API被重定向到登錄頁面了;

登出訪問API

通過本案例吮便,我們學(xué)會了如何使用Spring Security進(jìn)行基本的訪問限制和自定義登錄頁面笔呀。


用戶管理;

用戶管理有幾種方式:

1. 在resources底下的application.properties內(nèi)配置可登錄的用戶信息:

spring.security.user.name=dylanz
spring.security.user.password=666
這種方式有個弊端:只能配置一個用戶信息髓需;

2. 在config底下的WebSecurityConfig配置類內(nèi)添加可登錄的用戶信息userDetailsService许师,如:

package com.github.dylanz666.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/", "/home.html").permitAll()//這2個url不用訪問認(rèn)證
                .anyRequest().authenticated()//其他url都需要訪問認(rèn)證
                .and()
                .formLogin()
                .loginPage("/login.html")//登錄頁面的url
                .loginProcessingUrl("/login")//登錄表使用的API
                .permitAll()//login.html和login不需要訪問認(rèn)證
                .and()
                .logout()
                .permitAll();//logout不需要訪問認(rèn)證
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails dylanz =
                User.withUsername("dylanz")
                        .password(bCryptPasswordEncoder.encode("666"))
                        .roles("ADMIN")
                        .build();
        return new InMemoryUserDetailsManager(user);
    }
}

3. WebSecurityConfig配置類內(nèi)可配置多個可登錄的用戶信息:

package com.github.dylanz666.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/", "/home.html").permitAll()//這2個url不用訪問認(rèn)證
                .anyRequest().authenticated()//其他url都需要訪問認(rèn)證
                .and()
                .formLogin()
                .loginPage("/login.html")//登錄頁面的url
                .loginProcessingUrl("/login")//登錄表使用的API
                .permitAll()//login.html和login不需要訪問認(rèn)證
                .and()
                .logout()
                .permitAll();//logout不需要訪問認(rèn)證
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        UserDetails dylanz =
                User.withUsername("dylanz")
                        .password(bCryptPasswordEncoder.encode("666"))
                        .roles("ADMIN")
                        .build();
        UserDetails ritay =
                User.withUsername("ritay")
                        .password(bCryptPasswordEncoder.encode("888"))
                        .roles("USER")
                        .build();
        UserDetails jonathanw =
                User.withUsername("jonathanw")
                        .password(bCryptPasswordEncoder.encode("999"))
                        .roles("USER")
                        .build();
        return new InMemoryUserDetailsManager(dylanz, ritay, jonathanw);
    }
}
我在WebSecurityConfig配置類內(nèi)設(shè)置了3個可登錄的用戶,我們可以通過這種方式相對靈活的添加N個用戶僚匆。

4. 在數(shù)據(jù)庫中保存可登錄的用戶信息:

這是更常見的保存用戶信息的方式微渠,我們?nèi)砸宰詈唵蔚姆绞絹鞤emo從中心化的用戶信息池獲取用戶信息,即:模擬數(shù)據(jù)庫查詢過程咧擂;
1). 項(xiàng)目下創(chuàng)建domain包逞盆、service包;
2). domain包內(nèi)創(chuàng)建User實(shí)體類松申、service包下創(chuàng)建UserDetailsImpl類和UserDetailsServiceImpl類云芦;

創(chuàng)建類

3). 編寫User實(shí)體類;

package com.github.dylanz666.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @author : dylanz
 * @since : 08/31/2020
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;
    private String password;
}

4). 編寫UserDetailsImpl類贸桶;

package com.github.dylanz666.service;

import com.github.dylanz666.domain.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author : dylanz
 * @since : 08/31/2020
 */
@Service
public class UserDetailsImpl implements UserDetails {
    private User currentUser;

    public UserDetailsImpl() {
    }

    public UserDetailsImpl(User user) {
        if (user != null) {
            this.currentUser = user;
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority("admin");
        authorities.add(authority);
        return authorities;
    }

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

    public String getUsername() {
        return currentUser.getUsername();
    }

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

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

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

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

5). 編寫UserDetailsServiceImpl類舅逸;

package com.github.dylanz666.service;

import com.github.dylanz666.domain.User;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

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

/**
 * @author : dylanz
 * @since : 08/31/2020
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserDetailsImpl userService;
    @Autowired
    private UserDetails userDetails;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //Spring Security要求必須加密密碼
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        //模擬從數(shù)據(jù)庫中取出用戶信息,使用的sql如: SELECT * FROM USER WHERE USER_NAME='cherrys'
        List<User> userList = new ArrayList<>();
        User firstUser = new User();
        firstUser.setUsername("cherrys");
        firstUser.setPassword(passwordEncoder.encode("123"));
        userList.add(firstUser);
        User secondUser = new User();
        secondUser.setUsername("randyh");
        secondUser.setPassword(passwordEncoder.encode("456"));
        userList.add(secondUser);

        List<User> mappedUsers = userList.stream().filter(s -> s.getUsername().equals(username)).collect(Collectors.toList());

        //判斷用戶是否存在
        User user;
        if (CollectionUtils.isEmpty(mappedUsers)) {
            logger.info(String.format("The user %s is not found !", username));
            throw new UsernameNotFoundException(String.format("The user %s is not found !", username));
        }
        user = mappedUsers.get(0);
        return new UserDetailsImpl(user);
    }
}

解釋一下:

  • UserDetailsServiceImpl: 用于模擬從數(shù)據(jù)庫查詢出用戶信息皇筛,且模擬數(shù)據(jù)庫中存儲了加密的字符串琉历;

  • UserDetailsImpl:用于使用從數(shù)據(jù)庫查詢出的用戶信息,設(shè)置可登錄的用戶名设联、密碼善已,設(shè)置過程要配合使用WebSecurityConfig灼捂;

6). 修改WebSecurityConfig配置類;

package com.github.dylanz666.config;

import com.github.dylanz666.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author : dylanz
 * @since : 08/30/2020
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/", "/home.html").permitAll()//這2個url不用訪問認(rèn)證
                .anyRequest().authenticated()//其他url都需要訪問認(rèn)證
                .and()
                .formLogin()
                .loginPage("/login.html")//登錄頁面的url
                .loginProcessingUrl("/login")//登錄表使用的API
                .permitAll()//login.html和login不需要訪問認(rèn)證
                .and()
                .logout()
                .permitAll();//logout不需要訪問認(rèn)證
        httpSecurity.userDetailsService(userDetailsService());
        httpSecurity.userDetailsService(userDetailsService);
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        UserDetails dylanz =
                User.withUsername("dylanz")
                        .password(bCryptPasswordEncoder.encode("666"))
                        .roles("ADMIN")
                        .build();
        UserDetails ritay =
                User.withUsername("ritay")
                        .password(bCryptPasswordEncoder.encode("888"))
                        .roles("USER")
                        .build();
        UserDetails jonathanw =
                User.withUsername("jonathanw")
                        .password(bCryptPasswordEncoder.encode("999"))
                        .roles("USER")
                        .build();
        return new InMemoryUserDetailsManager(dylanz, ritay, jonathanw);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

特別注意:

  • 必須在WebSecurityConfig中聲明PasswordEncoder换团;

  • 在WebSecurityConfig的configure方法中使用:

httpSecurity.userDetailsService(userDetailsService);

至此悉稠,我們在內(nèi)存中添加了dylanz,ritay艘包,jonathanw三個用戶的猛,并且數(shù)據(jù)庫中也存儲了cherrys、randyh兩個用戶想虎,一共5個用戶卦尊;

我們來測試一下:

randyh+正確密碼1
randyh+正確密碼2
randyh+錯誤密碼
dylanz+正確密碼1
dylanz+正確密碼2
dylanz+錯誤密碼
不存在的賬戶

這個認(rèn)證過程還是比較初級的,真實(shí)案例中會比這個認(rèn)證過程復(fù)雜許多舌厨,我們開卷有益岂却,再接再厲!


如果本文對您有幫助裙椭,麻煩動動手指點(diǎn)點(diǎn)贊躏哩?

謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揉燃,一起剝皮案震驚了整個濱河市扫尺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌炊汤,老刑警劉巖正驻,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抢腐,居然都是意外死亡姑曙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門氓栈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渣磷,“玉大人,你說我怎么就攤上這事授瘦〈捉纾” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵提完,是天一觀的道長形纺。 經(jīng)常有香客問我,道長徒欣,這世上最難降的妖魔是什么逐样? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上脂新,老公的妹妹穿的比我還像新娘挪捕。我一直安慰自己,他們只是感情好争便,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布级零。 她就那樣靜靜地躺著,像睡著了一般滞乙。 火紅的嫁衣襯著肌膚如雪奏纪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天斩启,我揣著相機(jī)與錄音序调,去河邊找鬼。 笑死兔簇,一個胖子當(dāng)著我的面吹牛发绢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播男韧,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼朴摊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了此虑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤口锭,失蹤者是張志新(化名)和其女友劉穎朦前,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹃操,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡韭寸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荆隘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恩伺。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖椰拒,靈堂內(nèi)的尸體忽然破棺而出晶渠,到底是詐尸還是另有隱情,我是刑警寧澤燃观,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布褒脯,位于F島的核電站,受9級特大地震影響缆毁,放射性物質(zhì)發(fā)生泄漏番川。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颁督。 院中可真熱鬧践啄,春花似錦、人聲如沸沉御。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚷节。三九已至聂儒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硫痰,已是汗流浹背衩婚。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留效斑,地道東北人非春。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像缓屠,于是被迫代替她去往敵國和親奇昙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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