Spring Security項目涉及到的重要類說明以及可以使用的地方

Spring Security 介紹

Spring Security 應(yīng)該屬于 Spring 全家桶中學(xué)習(xí)曲線比較陡峭的幾個模塊之一核芽,下面我將從起源和定義這兩個方面來簡單介紹一下它铝噩。

起源:?Spring Security 實際上起源于 Acegi Security,這個框架能為基于 Spring 的企業(yè)應(yīng)用提供強(qiáng)大而靈活安全訪問控制解決方案碌冶,并且框架這個充分利用 Spring 的 IoC 和 AOP 功能悦陋,提供聲明式安全訪問控制的功能么鹤。后面案狠,隨著這個項目發(fā)展, Acegi Security 成為了Spring官方子項目卷雕,后來被命名為 “Spring Security”节猿。

**定義:**Spring Security 是一個功能強(qiáng)大且高度可以定制的框架,側(cè)重于為Java 應(yīng)用程序提供身份驗證和授權(quán)爽蝴°迮——官方介紹。

Session 和 Token 認(rèn)證對比

Session 認(rèn)證圖解

很多時候我們都是通過 SessionID 來實現(xiàn)特定的用戶蝎亚,SessionID 一般會選擇存放在 Redis 中九孩。舉個例子:用戶成功登陸系統(tǒng),然后返回給客戶端具有 SessionID 的 Cookie发框,當(dāng)用戶向后端發(fā)起請求的時候會把 SessionID 帶上躺彬,這樣后端就知道你的身份狀態(tài)了。

關(guān)于這種認(rèn)證方式更詳細(xì)的過程如下:

用戶向服務(wù)器發(fā)送用戶名和密碼用于登陸系統(tǒng)梅惯。

服務(wù)器驗證通過后宪拥,服務(wù)器為用戶創(chuàng)建一個 Session,并將 Session信息存儲 起來铣减。

服務(wù)器向用戶返回一個 SessionID她君,寫入用戶的 Cookie。

當(dāng)用戶保持登錄狀態(tài)時葫哗,Cookie 將與每個后續(xù)請求一起被發(fā)送出去缔刹。

服務(wù)器可以將存儲在 Cookie 上的 Session ID 與存儲在內(nèi)存中或者數(shù)據(jù)庫中的 Session 信息進(jìn)行比較,以驗證用戶的身份劣针,返回給用戶客戶端響應(yīng)信息的時候會附帶用戶當(dāng)前的狀態(tài)校镐。

Token 認(rèn)證圖解

在基于 Token 進(jìn)行身份驗證的的應(yīng)用程序中,服務(wù)器通過Payload捺典、Header和一個密鑰(secret)創(chuàng)建令牌(Token)并將 Token 發(fā)送給客戶端鸟廓,客戶端將 Token 保存在 Cookie 或者 localStorage 里面,以后客戶端發(fā)出的所有請求都會攜帶這個令牌襟己。你可以把它放在 Cookie 里面自動發(fā)送引谜,但是這樣不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization字段中: Authorization: Bearer Token擎浴。

關(guān)于這種認(rèn)證方式更詳細(xì)的過程如下:

用戶向服務(wù)器發(fā)送用戶名和密碼用于登陸系統(tǒng)煌张。

身份驗證服務(wù)響應(yīng)并返回了簽名的 JWT,上面包含了用戶是誰的內(nèi)容退客。

用戶以后每次向后端發(fā)請求都在 Header 中帶上 JWT。

服務(wù)端檢查 JWT 并從中獲取用戶相關(guān)信息。

項目涉及到的重要類說明

配置類

在本項目中我們自定義 SecurityConfig 繼承了 WebSecurityConfigurerAdapter萌狂。 WebSecurityConfigurerAdapter提供HttpSecurity來配置 cors档玻,csrf,會話管理和受保護(hù)資源的規(guī)則茫藏。

配置類中我們主要配置了:

密碼編碼器 BCryptPasswordEncoder(存入數(shù)據(jù)庫的密碼需要被加密)误趴。

為 AuthenticationManager 設(shè)置自定義的 UserDetailsService以及密碼編碼器;

在 Spring Security 配置指定了哪些路徑下的資源需要驗證了的用戶才能訪問务傲、哪些不需要以及哪些資源只能被特定角色訪問凉当;

將我們自定義的兩個過濾器添加到 Spring Security 配置中;

將兩個自定義處理權(quán)限認(rèn)證方面的異常類添加到 Spring Security 配置中售葡;

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired

UserDetailsServiceImpl userDetailsServiceImpl;

/**

* 密碼編碼器

*/

@Bean

public BCryptPasswordEncoder bCryptPasswordEncoder() {

return new BCryptPasswordEncoder();

}

@Bean

public UserDetailsService createUserDetailsService() {

return userDetailsServiceImpl;

}

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// 設(shè)置自定義的userDetailsService以及密碼編碼器

auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(bCryptPasswordEncoder());

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http.cors().and()

// 禁用 CSRF

.csrf().disable()

.authorizeRequests()

.antMatchers(HttpMethod.POST, "/auth/login").permitAll()

// 指定路徑下的資源需要驗證了的用戶才能訪問

.antMatchers("/api/**").authenticated()

.antMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")

// 其他都放行了

.anyRequest().permitAll()

.and()

//添加自定義Filter

.addFilter(new JWTAuthenticationFilter(authenticationManager()))

.addFilter(new JWTAuthorizationFilter(authenticationManager()))

// 不需要session(不創(chuàng)建會話)

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

// 授權(quán)異常處理

.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())

.accessDeniedHandler(new JWTAccessDeniedHandler());

}

}

跨域:

在這里踩的一個坑是:如果你沒有設(shè)置exposedHeaders("Authorization")暴露 header 中的"Authorization"屬性給客戶端應(yīng)用程序的話看杭,前端是獲取不到 token 信息的。

@Configuration

public class CorsConfiguration implements WebMvcConfigurer {

@Override

public void addCorsMappings(CorsRegistry registry) {

registry.addMapping("/**")

.allowedOrigins("*")

//暴露header中的其他屬性給客戶端應(yīng)用程序

//如果不設(shè)置這個屬性前端無法通過response header獲取到Authorization也就是token

.exposedHeaders("Authorization")

.allowCredentials(true)

.allowedMethods("GET", "POST", "DELETE", "PUT")

.maxAge(3600);

}

}

工具類

/**

* @author shuang.kou

*/

public class JwtTokenUtils {

/**

* 生成足夠的安全隨機(jī)密鑰挟伙,以適合符合規(guī)范的簽名

*/

private static byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SecurityConstants.JWT_SECRET_KEY);

private static SecretKey secretKey = Keys.hmacShaKeyFor(apiKeySecretBytes);

public static String createToken(String username, List roles, boolean isRememberMe) {

long expiration = isRememberMe ? SecurityConstants.EXPIRATION_REMEMBER : SecurityConstants.EXPIRATION;

String tokenPrefix = Jwts.builder()

.setHeaderParam("typ", SecurityConstants.TOKEN_TYPE)

.signWith(secretKey, SignatureAlgorithm.HS256)

.claim(SecurityConstants.ROLE_CLAIMS, String.join(",", roles))

.setIssuer("SnailClimb")

.setIssuedAt(new Date())

.setSubject(username)

.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))

.compact();

return SecurityConstants.TOKEN_PREFIX + tokenPrefix;

}

private boolean isTokenExpired(String token) {

Date expiredDate = getTokenBody(token).getExpiration();

return expiredDate.before(new Date());

}

public static String getUsernameByToken(String token) {

return getTokenBody(token).getSubject();

}

/**

* 獲取用戶所有角色

*/

public static List getUserRolesByToken(String token) {

String role = (String) getTokenBody(token)

.get(SecurityConstants.ROLE_CLAIMS);

return Arrays.stream(role.split(","))

.map(SimpleGrantedAuthority::new)

.collect(Collectors.toList());

}

private static Claims getTokenBody(String token) {

return Jwts.parser()

.setSigningKey(secretKey)

.parseClaimsJws(token)

.getBody();

}

}

獲取保存在服務(wù)端的用戶信息類

Spring Security 提供的 UserDetailsService有一個通過名字返回 Spring Security 可用于身份驗證的UserDetails對象的方法:loadUserByUsername()楼雹。

package org.springframework.security.core.userdetails;

/**

*加載用戶特定數(shù)據(jù)的核心接口。

*/

public interface UserDetailsService {

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

UserDetails包含用于構(gòu)建認(rèn)證對象的必要信息(例如:用戶名尖阔,密碼)贮缅。

package org.springframework.security.core.userdetails;

/**

*提供用戶核心信息的借口

*/

public interface UserDetails extends Serializable {

Collection getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();

}

一般情況下我們需要實現(xiàn) UserDetailsService 借口并重寫其中的 loadUserByUsername() 方法。

@Service

public class UserDetailsServiceImpl implements UserDetailsService {

private final UserService userService;

public UserDetailsServiceImpl(UserService userService) {

this.userService = userService;

}

@Override

public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {

User user = userService.findUserByUserName(name);

return new JwtUser(user);

}

}

認(rèn)證過濾器(重要)

第一個過濾器主要JWTAuthenticationFilter用于根據(jù)用戶的用戶名和密碼進(jìn)行登錄驗證(用戶請求中必須有用戶名和密碼這兩個參數(shù))介却,為此我們繼承了 UsernamePasswordAuthenticationFilter 并且重寫了下面三個方法:

attemptAuthentication(): 驗證用戶身份谴供。

successfulAuthentication() : 用戶身份驗證成功后調(diào)用的方法。

unsuccessfulAuthentication(): 用戶身份驗證失敗后調(diào)用的方法齿坷。

/**

* @author shuang.kou

* 如果用戶名和密碼正確桂肌,那么過濾器將創(chuàng)建一個JWT Token 并在HTTP Response 的header中返回它,格式:token: "Bearer +具體token值"

*/

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

private ThreadLocal rememberMe = new ThreadLocal<>();

private AuthenticationManager authenticationManager;

public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {

this.authenticationManager = authenticationManager;

// 設(shè)置登錄請求的 URL

super.setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);

}

@Override

public Authentication attemptAuthentication(HttpServletRequest request,

HttpServletResponse response) throws AuthenticationException {

ObjectMapper objectMapper = new ObjectMapper();

try {

// 從輸入流中獲取到登錄的信息

LoginUser loginUser = objectMapper.readValue(request.getInputStream(), LoginUser.class);

rememberMe.set(loginUser.getRememberMe());

// 這部分和attemptAuthentication方法中的源碼是一樣的胃夏,

// 只不過由于這個方法源碼的是把用戶名和密碼這些參數(shù)的名字是死的轴或,所以我們重寫了一下

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(

loginUser.getUsername(), loginUser.getPassword());

return authenticationManager.authenticate(authRequest);

} catch (IOException e) {

e.printStackTrace();

return null;

}

}

/**

* 如果驗證成功,就生成token并返回

*/

@Override

protected void successfulAuthentication(HttpServletRequest request,

HttpServletResponse response,

FilterChain chain,

Authentication authentication) {

JwtUser jwtUser = (JwtUser) authentication.getPrincipal();

List roles = jwtUser.getAuthorities()

.stream()

.map(GrantedAuthority::getAuthority)

.collect(Collectors.toList());

// 創(chuàng)建 Token

String token = JwtTokenUtils.createToken(jwtUser.getUsername(), roles, rememberMe.get());

// Http Response Header 中返回 Token

response.setHeader(SecurityConstants.TOKEN_HEADER, token);

}

@Override

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException {

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());

}

}

這個過濾器中有幾個比較重要的地方說明:

1.UsernamePasswordAuthenticationToken:從登錄請求中獲取{用戶名仰禀,密碼}照雁,AuthenticationManager將使用它來認(rèn)證登錄帳戶。

2.authenticationManager.authenticate(authRequest):這段代碼主要對用戶進(jìn)行認(rèn)證答恶,當(dāng)執(zhí)行這段代碼的時候會跳到UserDetailsServiceImpl中去調(diào)用loadUserByUsername()方法來驗證(我們在配置類中配置了AuthenticationManager使用自定義的UserDetailsServiceImpl去驗證用戶信息)饺蚊。當(dāng)驗證成功后會返回一個完整填充的Authentication對象(包括授予的權(quán)限),然后會去調(diào)用successfulAuthentication方法悬嗓。

package org.springframework.security.authentication;

/**

*嘗試驗證Authentication對象污呼,如果成功,將返回一個完整填充的Authentication對象(包括授予的權(quán)限)包竹。

*/

public interface AuthenticationManager {

Authentication authenticate(Authentication authentication)

throws AuthenticationException;

}

授權(quán)過濾器(重要)

這個過濾器繼承了 BasicAuthenticationFilter燕酷,主要用于處理身份認(rèn)證后才能訪問的資源籍凝,它會檢查 HTTP 請求是否存在帶有正確令牌的 Authorization 標(biāo)頭并驗證 token 的有效性。

當(dāng)用戶使用 token 對需要權(quán)限才能訪問的資源進(jìn)行訪問的時候苗缩,這個類是主要用到的饵蒂,下面按照步驟來說一說每一步到底都做了什么。

1.當(dāng)用戶使用系統(tǒng)返回的 token 信息進(jìn)行登錄的時候 酱讶,會首先經(jīng)過doFilterInternal()方法退盯,這個方法會從請求的Header中取出 token 信息,然后判斷 token 信息是否為空以及 token 信息格式是否正確泻肯。

2.如果請求頭中有token 并且 token 的格式正確渊迁,則進(jìn)行解析并判斷 token 的有效性,然后會在 Spring Security 全局設(shè)置授權(quán)信息SecurityContextHolder.getContext().setAuthentication(getAuthentication(authorization));

/**

* 過濾器處理所有HTTP請求灶挟,并檢查是否存在帶有正確令牌的Authorization標(biāo)頭琉朽。例如,如果令牌未過期或簽名密鑰正確膏萧。

*

* @author shuang.kou

*/

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

private static final Logger logger = Logger.getLogger(JWTAuthorizationFilter.class.getName());

public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {

super(authenticationManager);

}

@Override

protected void doFilterInternal(HttpServletRequest request,

HttpServletResponse response,

FilterChain chain) throws IOException, ServletException {

String authorization = request.getHeader(SecurityConstants.TOKEN_HEADER);

// 如果請求頭中沒有Authorization信息則直接放行了

if (authorization == null || !authorization.startsWith(SecurityConstants.TOKEN_PREFIX)) {

chain.doFilter(request, response);

return;

}

// 如果請求頭中有token漓骚,則進(jìn)行解析,并且設(shè)置授權(quán)信息

SecurityContextHolder.getContext().setAuthentication(getAuthentication(authorization));

super.doFilterInternal(request, response, chain);

}

/**

* 這里從token中獲取用戶信息并新建一個token

*/

private UsernamePasswordAuthenticationToken getAuthentication(String authorization) {

String token = authorization.replace(SecurityConstants.TOKEN_PREFIX, "");

try {

String username = JwtTokenUtils.getUsernameByToken(token);

// 通過 token 獲取用戶具有的角色

List userRolesByToken = JwtTokenUtils.getUserRolesByToken(token);

if (!StringUtils.isEmpty(username)) {

return new UsernamePasswordAuthenticationToken(username, null, userRolesByToken);

}

} catch (SignatureException | ExpiredJwtException exception) {

logger.warning("Request to parse JWT with invalid signature . Detail : " + exception.getMessage());

}

return null;

}

}

獲取當(dāng)前用戶

我們在講過濾器的時候說過榛泛,當(dāng)認(rèn)證成功的用戶訪問系統(tǒng)的時候蝌蹂,它的認(rèn)證信息會被設(shè)置在 Spring Security 全局中。那么曹锨,既然這樣孤个,我們在其他地方獲取到當(dāng)前登錄用戶的授權(quán)信息也就很簡單了,通過SecurityContextHolder.getContext().getAuthentication();方法即可沛简。

SecurityContextHolder 保存 SecurityContext 的信息齐鲤,SecurityContext 保存已通過認(rèn)證的 Authentication 認(rèn)證信息。

為此椒楣,我們實現(xiàn)了一個專門用來獲取當(dāng)前用戶的類:

/**

* @author shuang.kou

* 獲取當(dāng)前請求的用戶

*/

@Component

public class CurrentUser {

private final UserDetailsServiceImpl userDetailsService;

public CurrentUser(UserDetailsServiceImpl userDetailsService) {

this.userDetailsService = userDetailsService;

}

public JwtUser getCurrentUser() {

return (JwtUser) userDetailsService.loadUserByUsername(getCurrentUserName());

}

/**

* TODO:由于在JWTAuthorizationFilter這個類注入UserDetailsServiceImpl一致失敗给郊,

* 導(dǎo)致無法正確查找到用戶,所以存入Authentication的Principal為從 token 中取出的當(dāng)前用戶的姓名

*/

private static String getCurrentUserName() {

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication != null && authentication.getPrincipal() != null) {

return (String) authentication.getPrincipal();

}

return null;

}

}

異常相關(guān)

AccessDeniedHandler

JWTAccessDeniedHandler實現(xiàn)了AccessDeniedHandler主要用來解決認(rèn)證過的用戶訪問需要權(quán)限才能訪問的資源時的異常捧灰。

/**

* @author shuang.kou

* AccessDeineHandler 用來解決認(rèn)證過的用戶訪問需要權(quán)限才能訪問的資源時的異常

*/

public class JWTAccessDeniedHandler implements AccessDeniedHandler {

/**

* 當(dāng)用戶嘗試訪問需要權(quán)限才能的REST資源而權(quán)限不足的時候淆九,

* 將調(diào)用此方法發(fā)送401響應(yīng)以及錯誤信息

*/

@Override

public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {

accessDeniedException = new AccessDeniedException("Sorry you don not enough permissions to access it!");

response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());

}

}

AuthenticationEntryPoint

JWTAuthenticationEntryPoint 實現(xiàn)了 AuthenticationEntryPoint 用來解決匿名用戶訪問需要權(quán)限才能訪問的資源時的異常

/**

* @author shuang.kou

* AuthenticationEntryPoint 用來解決匿名用戶訪問需要權(quán)限才能訪問的資源時的異常

*/

public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {

/**

* 當(dāng)用戶嘗試訪問需要權(quán)限才能的REST資源而不提供Token或者Token過期時,

* 將調(diào)用此方法發(fā)送401響應(yīng)以及錯誤信息

*/

@Override

public void commence(HttpServletRequest request,

HttpServletResponse response,

AuthenticationException authException) throws IOException {

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());

}

}

驗證權(quán)限配置的 Controller

這個是 UserControler 主要用來檢測權(quán)限配置是否生效毛俏。

getAllUser()方法被注解 @PreAuthorize("hasAnyRole('ROLE_DEV','ROLE_PM')")修飾代表這個方法可以被DEV炭庙,PM 這兩個角色訪問,而deleteUserById() 被注解 @PreAuthorize("hasAnyRole('ROLE_ADMIN')")修飾代表只能被 ADMIN 訪問煌寇。

/**

* @author shuang.kou

*/

@RestController

@RequestMapping("/api")

public class UserController {

private final UserService userService;

private final CurrentUser currentUser;

public UserController(UserService userService, CurrentUser currentUser) {

this.userService = userService;

this.currentUser = currentUser;

}

@GetMapping("/users")

@PreAuthorize("hasAnyRole('ROLE_DEV','ROLE_PM')")

public ResponseEntity> getAllUser(@RequestParam(value = "pageNum", defaultValue = "0") int pageNum, @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {

System.out.println("當(dāng)前訪問該接口的用戶為:" + currentUser.getCurrentUser().toString());

Page allUser = userService.getAllUser(pageNum, pageSize);

return ResponseEntity.ok().body(allUser);

}

@DeleteMapping("/user")

@PreAuthorize("hasAnyRole('ROLE_ADMIN')")

public ResponseEntity deleteUserById(@RequestParam("username") String username) {

userService.deleteUserByUserName(username);

return ResponseEntity.ok().build();

}

}

歡迎大家點贊關(guān)注焕蹄,轉(zhuǎn)發(fā),此外給大家整理了一些詳細(xì)的資料阀溶,私信我“資料”即可獲取

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腻脏,一起剝皮案震驚了整個濱河市鸦泳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迹卢,老刑警劉巖辽故,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腐碱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掉弛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門症见,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人殃饿,你說我怎么就攤上這事谋作。” “怎么了乎芳?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵遵蚜,是天一觀的道長。 經(jīng)常有香客問我奈惑,道長吭净,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任肴甸,我火速辦了婚禮寂殉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘原在。我一直安慰自己友扰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布庶柿。 她就那樣靜靜地躺著村怪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浮庐。 梳的紋絲不亂的頭發(fā)上甚负,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音兔辅,去河邊找鬼腊敲。 笑死,一個胖子當(dāng)著我的面吹牛维苔,可吹牛的內(nèi)容都是我干的碰辅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼介时,長吁一口氣:“原來是場噩夢啊……” “哼没宾!你這毒婦竟也來了凌彬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤循衰,失蹤者是張志新(化名)和其女友劉穎铲敛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體会钝,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡伐蒋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了迁酸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片先鱼。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奸鬓,靈堂內(nèi)的尸體忽然破棺而出焙畔,到底是詐尸還是另有隱情,我是刑警寧澤串远,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布宏多,位于F島的核電站,受9級特大地震影響澡罚,放射性物質(zhì)發(fā)生泄漏伸但。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一始苇、第九天 我趴在偏房一處隱蔽的房頂上張望砌烁。 院中可真熱鬧,春花似錦催式、人聲如沸函喉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽管呵。三九已至,卻和暖如春哺窄,著一層夾襖步出監(jiān)牢的瞬間捐下,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工萌业, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留坷襟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓生年,卻偏偏與公主長得像婴程,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抱婉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

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