我是這樣使用SpringBoot(多認(rèn)證方式)

目錄

有的時(shí)候,我們需要為用戶提供多種認(rèn)證方式。如:用戶名密碼登錄功蜓、手術(shù)號(hào)驗(yàn)證碼登錄园爷。下面實(shí)現(xiàn)Spring Security支持這兩種登錄方式。

增加Token

Spring Security默認(rèn)使用UsernamePasswordAuthenticationToken包裝登錄請(qǐng)求的信息式撼,Token繼承之AbstractAuthenticationToken童社。Spring Security將信息封裝成Token交給Provider處理。
這里增加一個(gè)MobileCodeAuthenticationToken類著隆,繼承之AbstractAuthenticationToken叠洗。用于封裝手機(jī)號(hào)驗(yàn)證碼的請(qǐng)求參數(shù)。后面會(huì)有相應(yīng)的Provider處理這個(gè)Token旅东。
創(chuàng)建包c(diǎn)om.biboheart.demos.security.tokens灭抑,在包下創(chuàng)建類MobileCodeAuthenticationToken〉执可以查看UsernamePasswordAuthenticationToken類源碼腾节,參考它完成類的代碼,內(nèi)容如下:

package com.biboheart.demos.security.tokens;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;
    private String credentials;

    public MobileCodeAuthenticationToken(Object principal, String credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    public MobileCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = null;
        super.setAuthenticated(true); // must use super, as we override
    }

    @Override
    public String getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }
}

Provider

Provider實(shí)現(xiàn)AuthenticationProvider接口荤牍,它執(zhí)行身份認(rèn)證工作案腺。前面用的是Spring Security默認(rèn)的Provider進(jìn)行認(rèn)證,我們沒(méi)有控制認(rèn)證過(guò)程康吵。在這里我們實(shí)現(xiàn)兩個(gè)Provider劈榨,UsernamePasswordAuthenticationProvider用于替換系統(tǒng)默認(rèn)的用戶名密碼認(rèn)證業(yè)務(wù),MobileCodeAuthenticationProvider用于執(zhí)行手機(jī)號(hào)驗(yàn)證碼認(rèn)證業(yè)務(wù)晦嵌。這兩個(gè)類創(chuàng)建在包c(diǎn)om.biboheart.demos.security.provider下同辣。實(shí)現(xiàn)接口AuthenticationProvider,其中Authentication authenticate函數(shù)用于執(zhí)行認(rèn)證惭载,supports函數(shù)用于篩選Token旱函,如果在這里返回true,所有Token都會(huì)認(rèn)證描滔。

package com.biboheart.demos.security.provider;

import com.biboheart.brick.utils.CheckUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.HashSet;
import java.util.Set;

public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
        String password = (String) authentication.getCredentials();
        // 認(rèn)證用戶名
        if (!"user".equals(username) && !"admin".equals(username)) {
            throw new BadCredentialsException("用戶不存在");
        }
        // 認(rèn)證密碼棒妨,暫時(shí)不加密
        if ("user".equals(username) && !"123".equals(password) || "admin".equals(username) && !"admin".equals(password)) {
            throw new BadCredentialsException("密碼不正確");
        }
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(username,
                authentication.getCredentials(), listUserGrantedAuthorities(username));
        result.setDetails(authentication.getDetails());
        return result;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }

    private Set<GrantedAuthority> listUserGrantedAuthorities(String username) {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        if (CheckUtils.isEmpty(username)) {
            return authorities;
        }
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        if ("admin".equals(username)) {
            authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        return authorities;
    }
}

MobileCodeAuthenticationProvider

package com.biboheart.demos.security.provider;

import com.biboheart.brick.utils.CheckUtils;
import com.biboheart.demos.security.tokens.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.HashSet;
import java.util.Set;

public class MobileCodeAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String mobile = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
        String code = (String) authentication.getCredentials();
        if (CheckUtils.isEmpty(code)) {
            throw new BadCredentialsException("驗(yàn)證碼不能為空");
        }
        if (!"13999990000".equals(mobile)) {
            throw new BadCredentialsException("用戶不存在");
        }
        // 手機(jī)號(hào)驗(yàn)證碼業(yè)務(wù)還沒(méi)有開(kāi)發(fā),先用4個(gè)0驗(yàn)證
        if (!code.equals("0000")) {
            throw new BadCredentialsException("驗(yàn)證碼不正確");
        }
        MobileCodeAuthenticationToken result = new MobileCodeAuthenticationToken(mobile,
                listUserGrantedAuthorities(mobile));
        result.setDetails(authentication.getDetails());
        return result;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (MobileCodeAuthenticationToken.class.isAssignableFrom(authentication));
    }

    private Set<GrantedAuthority> listUserGrantedAuthorities(String username) {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        if (CheckUtils.isEmpty(username)) {
            return authorities;
        }
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return authorities;
    }

}

完成Provider后含长,要將兩個(gè)Provider加入配置中券腔,使它們加入工作。修改SecurityConfiguration配置文件拘泞。首先實(shí)例化這兩個(gè)Provider纷纫,然后將兩Bean添加到configure(AuthenticationManagerBuilder auth)

package com.biboheart.demos.security;

import com.biboheart.demos.security.provider.MobileCodeAuthenticationProvider;
import com.biboheart.demos.security.provider.UsernamePasswordAuthenticationProvider;
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.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 創(chuàng)建內(nèi)存用戶
        /*auth.inMemoryAuthentication()
                .withUser("user").password(passwordEncoder.encode("123")).roles("USER")
                .and()
                .withUser("admin").password(passwordEncoder.encode("admin")).roles("USER", "ADMIN");*/
        auth
                .authenticationProvider(usernamePasswordAuthenticationProvider())
                .authenticationProvider(mobileCodeAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/", "/home").permitAll() // 這三個(gè)目錄不做安全控制
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")// 自定義的登錄頁(yè)面
                    .permitAll()
                    .and()
                .logout()
                    .logoutSuccessUrl("/");
    }

    @Bean
    public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
        return new UsernamePasswordAuthenticationProvider();
    }

    @Bean
    public MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {
        return new MobileCodeAuthenticationProvider();
    }

    // spring security 必須有一個(gè)passwordEncoder
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

增加過(guò)濾器

在這個(gè)時(shí)候,UsernamePasswordAuthenticationProvider已經(jīng)起作用了田弥。因?yàn)镾pring Security用默認(rèn)有一個(gè)UsernamePasswordAuthenticationFilter過(guò)濾器過(guò)濾login涛酗,在過(guò)濾器中會(huì)創(chuàng)建UsernamePasswordAuthenticationToken對(duì)象,UsernamePasswordAuthenticationProvider能夠得到Token進(jìn)行處理偷厦。雖然MobileCodeAuthenticationProvider已經(jīng)在認(rèn)證隊(duì)列中商叹,但是MobileCodeAuthenticationProvider是不會(huì)執(zhí)行認(rèn)證工作。MobileCodeAuthenticationToken是自定義的只泼,沒(méi)有地方生成它的實(shí)例剖笙,return (MobileCodeAuthenticationToken.class.isAssignableFrom(authentication));執(zhí)行完成這名后就漂過(guò)了。
參考Spring Security UsernamePasswordAuthenticationToken的認(rèn)證方式请唱,我們也在UsernamePasswordAuthenticationFilter之前加一個(gè)過(guò)濾器弥咪,用戶判斷MobileCodeAuthenticationToken認(rèn)證方式∈螅可以用指定的參數(shù)聚至,或者指定的URL,這里用的是URL判斷本橙,提供“/mobileCodeLogin”為手機(jī)號(hào)驗(yàn)證碼登錄的URL扳躬。這個(gè)Filter參考用戶名密碼的Filter實(shí)現(xiàn),名稱為MobileCodeAuthenticationFilter甚亭,從AbstractAuthenticationProcessingFilter繼承贷币。接收兩個(gè)參數(shù)分別為“mobile”和“code”。如果比較下亏狰,會(huì)發(fā)現(xiàn)與UsernamePasswordAuthenticationFilter非常像役纹。代碼如下:

package com.biboheart.demos.filter;

import com.biboheart.demos.security.tokens.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MobileCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
    public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";

    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
    private boolean postOnly = true;

    public MobileCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/mobileCodeLogin", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        
        String mobile = obtainMobile(request);
        String code = obtainCode(request);
        
        if (mobile == null) {
            mobile = "";
        }

        if (code == null) {
            code = "";
        }
        
        mobile = mobile.trim();
        code = code.trim();
        
        AbstractAuthenticationToken authRequest = new MobileCodeAuthenticationToken(mobile, code);
        
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }
    
    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter(codeParameter);
    }
    
    protected void setDetails(HttpServletRequest request,
            AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

}

完成Filter實(shí)現(xiàn)后,需要將它加入到Filter序列中暇唾。加入方法是在SecurityConfiguration文件中促脉,實(shí)例化Filter,然后在configure(HttpSecurity http)配置下加入http.addFilterBefore(mobileCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);即在UsernamePasswordAuthenticationFilter之前加入一個(gè)過(guò)濾器策州。記得將“/mobileCodeLogin”添加到允許通得中嘲叔。修改后的SecurityConfiguration如下:

package com.biboheart.demos.security;

import com.biboheart.demos.filter.MobileCodeAuthenticationFilter;
import com.biboheart.demos.security.provider.MobileCodeAuthenticationProvider;
import com.biboheart.demos.security.provider.UsernamePasswordAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 創(chuàng)建內(nèi)存用戶
        /*auth.inMemoryAuthentication()
                .withUser("user").password(passwordEncoder.encode("123")).roles("USER")
                .and()
                .withUser("admin").password(passwordEncoder.encode("admin")).roles("USER", "ADMIN");*/
        auth
                .authenticationProvider(usernamePasswordAuthenticationProvider())
                .authenticationProvider(mobileCodeAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
                .authorizeRequests()
                    .antMatchers("/", "/home", "/mobileCodeLogin").permitAll() // 這三個(gè)目錄不做安全控制
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")// 自定義的登錄頁(yè)面
                    .permitAll()
                    .and()
                .logout()
                    .logoutSuccessUrl("/");
        http.addFilterBefore(mobileCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        // @formatter:on
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public MobileCodeAuthenticationFilter mobileCodeAuthenticationFilter() {
        MobileCodeAuthenticationFilter filter = new MobileCodeAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    @Bean
    public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
        return new UsernamePasswordAuthenticationProvider();
    }

    @Bean
    public MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {
        return new MobileCodeAuthenticationProvider();
    }

    // spring security 必須有一個(gè)passwordEncoder
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

修改界面

在登錄界面中加入手機(jī)號(hào)驗(yàn)證碼登錄方式,試下效果抽活。手機(jī)號(hào)和驗(yàn)證碼在寫(xiě)在代碼中的硫戈,分別是13999990000和0000。登錄界面修改成:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Spring Security Example</title>
</head>
<body>
    密碼登錄:
    <hr/>
    <form th:action="@{/login}" method="post">
        <div>
            <label> 用戶名: <input type="text" name="username" />
            </label>
        </div>
        <div>
            <label> 密碼: <input type="password" name="password" />
            </label>
        </div>
        <div>
            <input type="submit" value="登錄" />
        </div>
    </form>
    <hr/>
    驗(yàn)證碼登錄:
    <hr/>
    <form th:action="@{/mobileCodeLogin}" method="post">
        <div>
            <label> 手機(jī)號(hào): <input type="text" name="mobile" />
            </label>
        </div>
        <div>
            <label> 驗(yàn)證碼: <input type="password" name="code" />
            </label>
        </div>
        <div>
            <input type="submit" value="登錄" />
        </div>
    </form>
</body>
</html>

完成開(kāi)發(fā)

如果需要更多的認(rèn)證方式下硕,同手機(jī)號(hào)驗(yàn)證碼丁逝。步驟如下:

  1. 創(chuàng)建Token,繼承之AbstractAuthenticationToken
  2. 創(chuàng)建Provider梭姓,實(shí)現(xiàn)AuthenticationProvider
  3. 創(chuàng)建Filter霜幼,繼承之AbstractAuthenticationProcessingFilter
  4. 在配置類中實(shí)例化Filter和Provider
  5. 在Filter中處理請(qǐng)求包裝Token
  6. Provider實(shí)例加入到auth.authenticationProvider
  7. 使用http.addFilterBefore在UsernamePasswordAuthenticationFilter之前加入Filter
    啟動(dòng)服務(wù),訪問(wèn)界面誉尖。使用流程與之前相同罪既。區(qū)別是登錄界面多了驗(yàn)證碼登錄表單,輸入13999990000,驗(yàn)證碼0000后也可以成功登錄琢感。
    登錄界面

    此時(shí)丢间,目錄結(jié)構(gòu)如下圖。
    目錄結(jié)構(gòu)

    源碼地址:https://gitee.com/biboheart/bh-springboot-demos.git
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驹针,一起剝皮案震驚了整個(gè)濱河市烘挫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柬甥,老刑警劉巖饮六,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異苛蒲,居然都是意外死亡卤橄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)臂外,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窟扑,“玉大人,你說(shuō)我怎么就攤上這事寄月」枷ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵漾肮,是天一觀的道長(zhǎng)厂抖。 經(jīng)常有香客問(wèn)我,道長(zhǎng)克懊,這世上最難降的妖魔是什么忱辅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮谭溉,結(jié)果婚禮上墙懂,老公的妹妹穿的比我還像新娘。我一直安慰自己扮念,他們只是感情好损搬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著柜与,像睡著了一般巧勤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弄匕,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天颅悉,我揣著相機(jī)與錄音,去河邊找鬼迁匠。 笑死剩瓶,一個(gè)胖子當(dāng)著我的面吹牛驹溃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播延曙,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼豌鹤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了搂鲫?” 一聲冷哼從身側(cè)響起傍药,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤磺平,失蹤者是張志新(化名)和其女友劉穎魂仍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拣挪,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擦酌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了菠劝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赊舶。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赶诊,靈堂內(nèi)的尸體忽然破棺而出笼平,到底是詐尸還是另有隱情,我是刑警寧澤舔痪,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布寓调,位于F島的核電站,受9級(jí)特大地震影響锄码,放射性物質(zhì)發(fā)生泄漏夺英。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一滋捶、第九天 我趴在偏房一處隱蔽的房頂上張望痛悯。 院中可真熱鬧,春花似錦重窟、人聲如沸载萌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扭仁。三九已至,卻和暖如春霎迫,著一層夾襖步出監(jiān)牢的瞬間斋枢,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工知给, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瓤帚,地道東北人描姚。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像戈次,于是被迫代替她去往敵國(guó)和親轩勘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355