【SpringSecurityOAuth2】源碼分析@EnableOAuth2Sso在Spring Security OAuth2 SSO單點(diǎn)登錄場(chǎng)景下的作用

一、從Spring Security OAuth2官方文檔了解@EnableOAuth2Sso作用

spring-security-oauth2-boot 2.2.0.RELEASE Single Sign On文檔地址

spring-security-oauth2-boot-2.2.0.RELEASE-single_sign_on-01

先從第一段介紹開始会烙,加上自己的分析:

  • @EnableOAuth2Sso是使用在OAuth2 Client角色上的注解负懦,從其包路徑也可以看出org.springframework.boot.autoconfigure.security.oauth2.client

  • @EnableOAuth2Sso單點(diǎn)登錄的原理簡(jiǎn)單來(lái)說(shuō)就是:標(biāo)注有@EnableOAuth2Sso的OAuth2 Client應(yīng)用在通過(guò)某種OAuth2授權(quán)流程獲取訪問(wèn)令牌后(一般是授權(quán)碼流程),通過(guò)訪問(wèn)令牌訪問(wèn)userDetails用戶明細(xì)這個(gè)受保護(hù)資源服務(wù)持搜,獲取用戶信息后密似,將用戶信息轉(zhuǎn)換為Spring Security上下文中的認(rèn)證后憑證Authentication,從而完成標(biāo)注有@EnableOAuth2Sso的OAuth2 Client應(yīng)用自身的登錄認(rèn)證的過(guò)程葫盼。整個(gè)過(guò)程是基于OAuth2的SSO單點(diǎn)登錄

  • SSO流程中需要訪問(wèn)的用戶信息資源地址残腌,可以通過(guò)security.oauth2.resource.userInfoUri配置指定

  • 最后的通過(guò)訪問(wèn)令牌訪問(wèn)受保護(hù)資源后,在當(dāng)前服務(wù)創(chuàng)建認(rèn)證后憑證Authentication(登錄態(tài))也可以不通過(guò)訪問(wèn)userInfoUri實(shí)現(xiàn)贫导,userInfoUri端點(diǎn)是需要用戶自己實(shí)現(xiàn)抛猫。默認(rèn)情況security.oauth2.resource.preferTokenInfo=true ,獲取用戶信息使用的是授權(quán)服務(wù)器的/check_token端點(diǎn)孩灯,即TokenInfo闺金,根據(jù)訪問(wèn)令牌找到在授權(quán)服務(wù)器關(guān)聯(lián)的授予這個(gè)訪問(wèn)令牌的用戶信息

  • Spring Security OAuth2 SSO整個(gè)流程實(shí)際上是 OAuth2 Client是一個(gè)運(yùn)行在Server上的Webapp的典型場(chǎng)景,很適合使用授權(quán)碼流程


spring-security-oauth2-boot-2.2.0.RELEASE-single_sign_on-02

第二段主要講了下如何使用@EnableOAuth2Sso

  • 使用@EnableOAuth2Sso的OAuth2 Client應(yīng)用可以使用/login端點(diǎn)用于觸發(fā)基于OAuth2的SSO流程峰档,這個(gè)入口地址也可以通過(guò)security.oauth2.sso.login-path來(lái)修改

  • 如果針對(duì)一些安全訪問(wèn)規(guī)則有自己的定制败匹,說(shuō)白了就是自己實(shí)現(xiàn)了Spring Security的WebSecurityConfigurerAdapter想自定義一些安全配置寨昙,但又想使用@EnableOAuth2Sso的特性,可以在自己的WebSecurityConfigurerAdapter上使用@EnableOAuth2Sso注解掀亩,注解會(huì)在你的安全配置基礎(chǔ)上做“增強(qiáng)”舔哪,至于具體如何“增強(qiáng)”的,后面的源碼分析部分會(huì)詳細(xì)解釋

    注意:

    如果是在自定義的AutoConfiguration自動(dòng)配置類上使用@EnableOAuth2Sso槽棍,在第一次重定向到授權(quán)服務(wù)器時(shí)會(huì)出現(xiàn)問(wèn)題捉蚤,具體是因?yàn)橥ㄟ^(guò)@EnableOAuth2Client添加的OAuth2ClientContextFilter會(huì)被放到springSecurityFilterChain這個(gè)Filter后面,導(dǎo)致無(wú)法攔截UserRedirectRequiredException需重定向異常

  • 如果沒(méi)有自己的WebSecurityConfigurerAdapter安全配置炼七,也可以在任意配置類上使用@EnableOAuth2Sso缆巧,除了添加OAuth2 SSO的增強(qiáng)外,還會(huì)有默認(rèn)的基本安全配置


二豌拙、源碼分析@EnableOAuth2Sso作用

首先來(lái)看一下@EnableOAuth2Sso的源碼

/**
 * Enable OAuth2 Single Sign On (SSO). If there is an existing
 * {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
 * {@code @EnableOAuth2Sso}, it is enhanced by adding an authentication filter and an
 * authentication entry point. If the user only has {@code @EnableOAuth2Sso} but not on a
 * WebSecurityConfigurerAdapter then one is added with all paths secured.
 *
 * @author Dave Syer
 * @since 1.3.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableOAuth2Client
@EnableConfigurationProperties(OAuth2SsoProperties.class)
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,
        ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {

}

可以看到主要做了幾件事

  • 添加@EnableOAuth2Client
  • 啟用OAuth2 SSO相關(guān)的OAuth2SsoProperties配置文件
  • 導(dǎo)入了3個(gè)配置類:OAuth2SsoDefaultConfiguration陕悬、OAuth2SsoCustomConfigurationResourceServerTokenServicesConfiguration


@EnableOAuth2Client

@EnableOAuth2Client從名稱就可以看出是專門給OAuth2 Client角色使用的注解按傅,其可以獨(dú)立使用墩莫,具體功能需要單獨(dú)寫一篇來(lái)分析,大致看一下源碼逞敷,主要是導(dǎo)入了OAuth2ClientConfiguration配置類

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(OAuth2ClientConfiguration.class)
public @interface EnableOAuth2Client {

}

OAuth2ClientConfiguration配置類主要做了三件事

  • 向Servlet容器添加OAuth2ClientContextFilter
  • 創(chuàng)建request scope的Spring Bean: AccessTokenRequest
  • 創(chuàng)建session scope的Spring Bean: OAuth2ClientContext狂秦,OAuth2 Client上下文

大體上就是為OAuth2 Client角色創(chuàng)建相關(guān)環(huán)境


OAuth2SsoCustomConfiguration:OAuth2 SSO自定義配置

/**
 * Configuration for OAuth2 Single Sign On (SSO) when there is an existing
 * {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
 * {@code @EnableOAuth2Sso}. The user-provided configuration is enhanced by adding an
 * authentication filter and an authentication entry point.
 *
 * @author Dave Syer
 */
@Configuration
@Conditional(EnableOAuth2SsoCondition.class)  //OAuth2 SSO自定義配置生效條件
public class OAuth2SsoCustomConfiguration
        implements ImportAware, BeanPostProcessor, ApplicationContextAware {

    private Class<?> configType;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        this.configType = ClassUtils.resolveClassName(importMetadata.getClassName(),
                null);

    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    /**
     * BeanPostProcessor的初始化后方法
     * 給用戶自定義的WebSecurityConfigurerAdapter添加Advice來(lái)增強(qiáng):SsoSecurityAdapter
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        // 如果是WebSecurityConfigurerAdapter,并且就是添加@EnableOAuth2Sso的那個(gè)
        if (this.configType.isAssignableFrom(bean.getClass())
                && bean instanceof WebSecurityConfigurerAdapter) {
            ProxyFactory factory = new ProxyFactory();
            factory.setTarget(bean);
            factory.addAdvice(new SsoSecurityAdapter(this.applicationContext));
            bean = factory.getProxy();
        }
        return bean;
    }

    /**
     * 攔截用戶的WebSecurityConfigurerAdapter
     * 在其init()初始化之前推捐,添加SsoSecurityConfigurer配置
     */
    private static class SsoSecurityAdapter implements MethodInterceptor {

        private SsoSecurityConfigurer configurer;

        SsoSecurityAdapter(ApplicationContext applicationContext) {
            this.configurer = new SsoSecurityConfigurer(applicationContext);
        }

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            if (invocation.getMethod().getName().equals("init")) {
                Method method = ReflectionUtils
                        .findMethod(WebSecurityConfigurerAdapter.class, "getHttp");
                ReflectionUtils.makeAccessible(method);
                HttpSecurity http = (HttpSecurity) ReflectionUtils.invokeMethod(method,
                        invocation.getThis());
                this.configurer.configure(http);
            }
            return invocation.proceed();
        }
    }
}

OAuth2SsoCustomConfiguration自定義配置指的是如果用戶有自定義的WebSecurityConfigurerAdapter安全配置的情況下裂问,就在用戶自定義配置的基礎(chǔ)上做OAuth2 SSO的增強(qiáng),具體分析為

  • 首先必須在滿足@Conditional(EnableOAuth2SsoCondition.class)的情況下才可以使用牛柒,EnableOAuth2SsoCondition條件指的是@EnableOAuth2Sso注解被使用在WebSecurityConfigurerAdapter
  • 可以看到OAuth2SsoCustomConfiguration配置類也是一個(gè)BeanPostProcessor堪簿,其會(huì)在Spring初始化Bean的前后做處理,上面代碼中會(huì)在Sping初始化WebSecurityConfigurerAdapter之后皮壁,并且就是添加了@EnableOAuth2Sso注解的WebSecurityConfigurerAdapter之后椭更,為安全配置類做“增強(qiáng)”,添加了一個(gè)Advice為SsoSecurityAdapter
  • SsoSecurityAdapter會(huì)在用戶添加了@EnableOAuth2Sso注解的WebSecurityConfigurerAdapter配置類調(diào)用init()初始化方法之前蛾魄,先添加一段子配置SsoSecurityConfigurer虑瀑,這個(gè)子配置就是實(shí)現(xiàn)基于OAuth2 SSO的關(guān)鍵


SsoSecurityConfigurer:OAuth2 SSO核心配置(增強(qiáng))

class SsoSecurityConfigurer {
        
    public void configure(HttpSecurity http) throws Exception {
        OAuth2SsoProperties sso = this.applicationContext
                .getBean(OAuth2SsoProperties.class);
        // Delay the processing of the filter until we know the
        // SessionAuthenticationStrategy is available:
        http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
        addAuthenticationEntryPoint(http, sso);
    }
    
  • 添加OAuth2ClientAuthenticationConfigurer子配置,為了向springSecurityFilterChain過(guò)濾器鏈添加一個(gè)專門用于處理OAuth2 SSO的OAuth2ClientAuthenticationProcessingFilter
  • 添加處理頁(yè)面及Ajax請(qǐng)求未認(rèn)證時(shí)的AuthenticationEntryPoint認(rèn)證入口

OAuth2ClientAuthenticationConfigurer子配置是重點(diǎn)

// 創(chuàng)建OAuth2ClientAuthenticationProcessingFilter
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
        OAuth2SsoProperties sso) {
    OAuth2RestOperations restTemplate = this.applicationContext
            .getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
    ResourceServerTokenServices tokenServices = this.applicationContext
            .getBean(ResourceServerTokenServices.class);
    OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
            sso.getLoginPath());
    filter.setRestTemplate(restTemplate);
    filter.setTokenServices(tokenServices);
    filter.setApplicationEventPublisher(this.applicationContext);
    return filter;
}

// OAuth2ClientAuthenticationConfigurer子配置
private static class OAuth2ClientAuthenticationConfigurer
        extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private OAuth2ClientAuthenticationProcessingFilter filter;

    OAuth2ClientAuthenticationConfigurer(
            OAuth2ClientAuthenticationProcessingFilter filter) {
        this.filter = filter;
    }

    @Override
    public void configure(HttpSecurity builder) throws Exception {
        OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
        ssoFilter.setSessionAuthenticationStrategy(
                builder.getSharedObject(SessionAuthenticationStrategy.class));
        // 添加過(guò)濾器
        builder.addFilterAfter(ssoFilter,
                AbstractPreAuthenticatedProcessingFilter.class);
    }

}

OAuth2ClientAuthenticationConfigurer子配置將構(gòu)造好的專門用于處理OAuth2 SSO場(chǎng)景的過(guò)濾器OAuth2ClientAuthenticationProcessingFilter添加到springSecurityFilterChain過(guò)濾器鏈中滴须,構(gòu)造這個(gè)Filter時(shí)需要

  • OAuth2RestOperations:專門用于和授權(quán)服務(wù)器舌狗、資源服務(wù)器做Rest交互的模板工具類
  • ResourceServerTokenServices:用于訪問(wèn)Token資源服務(wù)的類
  • SessionAuthenticationStrategy:OAuth2 SSO認(rèn)證完成后,使用Spring Security的會(huì)話策略

這一步扔水,向springSecurityFilterChain過(guò)濾器鏈中添加OAuth2ClientAuthenticationConfigurer是最核心的一步痛侍,整個(gè)OAuth2 SSO的交互都由這個(gè)Filter完成,OAuth2ClientAuthenticationConfigurer的具體邏輯待后續(xù)分析


OAuth2SsoDefaultConfiguration:OAuth2 SSO默認(rèn)配置

/**
 * Configuration for OAuth2 Single Sign On (SSO). If the user only has
 * {@code @EnableOAuth2Sso} but not on a {@code WebSecurityConfigurerAdapter} then one is
 * added with all paths secured.
 *
 * @author Dave Syer
 * @since 1.3.0
 */
@Configuration
@Conditional(NeedsWebSecurityCondition.class)  //OAuth2Sso默認(rèn)配置生效條件
public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter {

    private final ApplicationContext applicationContext;

    public OAuth2SsoDefaultConfiguration(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 1魔市、添加/**都需要認(rèn)證才能訪問(wèn)的限制
     * 2主届、添加SsoSecurityConfigurer配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
        new SsoSecurityConfigurer(this.applicationContext).configure(http);
    }

    /**
     * OAuth2Sso默認(rèn)配置生效條件
     */
    protected static class NeedsWebSecurityCondition extends EnableOAuth2SsoCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata));
        }
    }
}
  • 條件NeedsWebSecurityConditionEnableOAuth2SsoCondition相反赵哲,最后滿足當(dāng)用戶使用了EnableOAuth2Sso,但其沒(méi)有被放在自己定義的WebSecurityConfigurerAdapter安全配置類上時(shí)君丁,會(huì)進(jìn)入OAuth2 SSO默認(rèn)配置誓竿,從注釋信息也可以看出
  • OAuth2SsoDefaultConfiguration繼承了WebSecurityConfigurerAdapter,是一段Spring Security的安全配置
  • 添加滿足/**路徑的請(qǐng)求都需要authenticated()認(rèn)證谈截,默認(rèn)安全配置
  • 和上面分析一樣,使用SsoSecurityConfigurer子配置涧偷,最終會(huì)為springSecurityFilterChain過(guò)濾器鏈中添加OAuth2ClientAuthenticationConfigurer


ResourceServerTokenServicesConfiguration:訪問(wèn)Token資源服務(wù)的配置

主要作用是創(chuàng)建ResourceServerTokenServices簸喂,用于通過(guò)訪問(wèn)令牌獲取其相關(guān)的用戶憑據(jù),或者讀取訪問(wèn)令牌的完整信息燎潮,接口定義如下

public interface ResourceServerTokenServices {
    /**
     * Load the credentials for the specified access token.
     * 加載指定訪問(wèn)令牌的憑據(jù)
     *
     * @param accessToken The access token value.
     * @return The authentication for the access token.
     * @throws AuthenticationException If the access token is expired
     * @throws InvalidTokenException if the token isn't valid
     */
    OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;

    /**
     * Retrieve the full access token details from just the value.
     * 僅從值中檢索完整的訪問(wèn)令牌詳細(xì)信息
     * 
     * @param accessToken the token value
     * @return the full access token with client id etc.
     */
    OAuth2AccessToken readAccessToken(String accessToken);
}

具體的ResourceServerTokenServices接口實(shí)現(xiàn)分為

  • RemoteTokenServices:遠(yuǎn)端的TokenService
    • TokenInfoServices:訪問(wèn)/check_token端點(diǎn)喻鳄,根據(jù)訪問(wèn)令牌找到在授權(quán)服務(wù)器關(guān)聯(lián)的授予這個(gè)訪問(wèn)令牌的用戶信息
    • UserInfoTokenServices:訪問(wèn)用戶自定義的userInfo端點(diǎn),根據(jù)訪問(wèn)令牌訪問(wèn)受保護(hù)資源userInfo
  • JwtTokenServices:基于Json Web Token自包含令牌的TokenService

在通過(guò)以上ResourceServerTokenServices接口實(shí)現(xiàn)獲取用戶信息后确封,就可以在使用@EnableOAuth2Sso注解的OAuth2 Client上創(chuàng)建已認(rèn)證的用戶身份憑證Authentication除呵,完成登錄


三、總結(jié)

總的來(lái)說(shuō)@EnableOAuth2Sso注解幫助我們快速的將我們的OAuth2 Client應(yīng)用接入授權(quán)服務(wù)器完成基于OAuth2的SSO流程爪喘,創(chuàng)建登錄狀態(tài)

無(wú)論是用戶有沒(méi)有自己的WebSecurityConfigurerAdapter安全配置都可以使用@EnableOAuth2Sso注解颜曾,如果有,@EnableOAuth2Sso是在用戶的安全配置上做增強(qiáng)

增強(qiáng)的邏輯是在SpringSecurityFilterChain過(guò)濾器鏈上添加OAuth2ClientAuthenticationProcessingFilter這個(gè)用于登錄認(rèn)證的Filter秉剑,其使用的是OAuth2授權(quán)碼流程泛豪,以下都是這個(gè)Filter負(fù)責(zé)的功能

  • 將用戶重定向到授權(quán)服務(wù)器獲取授權(quán)
  • 根據(jù)code授權(quán)碼和OAuth2 clientId、secret獲取訪問(wèn)令牌
  • 最后使用ResourceServerTokenServices并攜帶訪問(wèn)令牌獲取用戶信息侦鹏,創(chuàng)建Authentication登錄后憑證诡曙,完成登錄
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市略水,隨后出現(xiàn)的幾起案子价卤,更是在濱河造成了極大的恐慌,老刑警劉巖渊涝,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慎璧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跨释,警方通過(guò)查閱死者的電腦和手機(jī)炸卑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)煤傍,“玉大人盖文,你說(shuō)我怎么就攤上這事◎悄罚” “怎么了五续?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵洒敏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我疙驾,道長(zhǎng)凶伙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任它碎,我火速辦了婚禮函荣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扳肛。我一直安慰自己傻挂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布挖息。 她就那樣靜靜地躺著金拒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪套腹。 梳的紋絲不亂的頭發(fā)上绪抛,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音电禀,去河邊找鬼幢码。 笑死,一個(gè)胖子當(dāng)著我的面吹牛尖飞,可吹牛的內(nèi)容都是我干的蛤育。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼葫松,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瓦糕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起腋么,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咕娄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后珊擂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圣勒,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年摧扇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了圣贸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扛稽,死狀恐怖吁峻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤用含,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布矮慕,位于F島的核電站,受9級(jí)特大地震影響啄骇,放射性物質(zhì)發(fā)生泄漏痴鳄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一缸夹、第九天 我趴在偏房一處隱蔽的房頂上張望痪寻。 院中可真熱鬧,春花似錦虽惭、人聲如沸橡类。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至佣蓉,卻和暖如春披摄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背勇凭。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工疚膊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虾标。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓寓盗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親璧函。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傀蚌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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