第十八章:SpringBoot項(xiàng)目中使用SpringSecurity整合OAuth2設(shè)計(jì)項(xiàng)目API安全接口服務(wù)

OAuth是一個(gè)關(guān)于授權(quán)的開(kāi)放網(wǎng)絡(luò)標(biāo)準(zhǔn)西饵,在全世界得到的廣泛的應(yīng)用汽纠,目前是2.0的版本。OAuth2在“客戶端”與“服務(wù)提供商”之間,設(shè)置了一個(gè)授權(quán)層(authorization layer)抹剩。“客戶端”不能直接登錄“服務(wù)提供商”栏饮,只能登錄授權(quán)層吧兔,以此將用戶與客戶端分離∨坻遥“客戶端”登錄需要OAuth提供的令牌境蔼,否則將提示認(rèn)證失敗而導(dǎo)致客戶端無(wú)法訪問(wèn)服務(wù)。下面我們就來(lái)講解下SpringBoot項(xiàng)目中是如何配置使用OAuth2服務(wù)器端伺通,并讓OAuth2整合SpringSecurity來(lái)保護(hù)我們的REST接口箍土。

免費(fèi)專題文章匯總

恒宇少年在博客整理出來(lái)了SpringBoot、ApiBoot罐监、SpringCloud的文章匯總【SpringBoot基礎(chǔ)教程專題】吴藻,【SpringCloud基礎(chǔ)教程專題】,【ApiBoot組件使用專題

本章目標(biāo)

基于SpringBoot項(xiàng)目提供一個(gè)繼承OAuth2安全框架的REST API服務(wù)端弓柱,必須獲取訪問(wèn)授權(quán)令牌后才可以訪問(wèn)資源沟堡。

OAuth2授權(quán)方式

我們?cè)谖恼麻_(kāi)始已經(jīng)說(shuō)過(guò)了侧但,我們的保護(hù)資源必須通過(guò)授權(quán)得到的令牌才可以訪問(wèn)。那么我們這個(gè)授權(quán)令牌要通過(guò)什么方式獲取呢航罗?

OAuth2為我們提供了四種授權(quán)方式:

1禀横、授權(quán)碼模式(authorization code)
2、簡(jiǎn)化模式(implicit)
3粥血、密碼模式(resource owner password credentials)
4柏锄、客戶端模式(client credentials)

授權(quán)碼模式

授權(quán)碼相對(duì)其他三種來(lái)說(shuō)是功能比較完整、流程最安全嚴(yán)謹(jǐn)?shù)氖跈?quán)方式复亏,通過(guò)客戶端的后臺(tái)服務(wù)器與服務(wù)提供商的認(rèn)證服務(wù)器交互來(lái)完成趾娃。流程如下圖2所示:

圖2

簡(jiǎn)化模式

這種模式不通過(guò)服務(wù)器端程序來(lái)完成,直接由瀏覽器發(fā)送請(qǐng)求獲取令牌缔御,令牌是完全暴露在瀏覽器中的抬闷,這種模式極力不推崇。流程如下圖3所示:

圖3

密碼模式

密碼模式也是比較常用到的一種耕突,客戶端向授權(quán)服務(wù)器提供用戶名饶氏、密碼然后得到授權(quán)令牌。這種模式不過(guò)有種弊端有勾,我們的客戶端需要存儲(chǔ)用戶輸入的密碼,但是對(duì)于用戶來(lái)說(shuō)信任度不高的平臺(tái)是不可能讓他們輸入密碼的古程。流程如下圖4所示:

圖4

客戶端模式

客戶端模式是客戶端以自己的名義去授權(quán)服務(wù)器申請(qǐng)授權(quán)令牌蔼卡,并不是完全意義上的授權(quán)。如下圖5所示:

圖5

上述簡(jiǎn)單的介紹了OAuth2內(nèi)部的四種授權(quán)方式挣磨,我們下面使用密碼模式來(lái)進(jìn)行測(cè)試雇逞,并且我們使用數(shù)據(jù)庫(kù)中的用戶數(shù)據(jù)來(lái)做驗(yàn)證處理,下面我們先來(lái)構(gòu)建項(xiàng)目茁裙。

構(gòu)建項(xiàng)目

我們使用IndeiiJ IDEA工具來(lái)構(gòu)建一個(gè)SpringBoot項(xiàng)目塘砸,目前最新版本的是1.5.3,應(yīng)該是昨天剛正式發(fā)布晤锥。項(xiàng)目我們預(yù)先引入幾個(gè)模塊掉蔬,Web、JPA矾瘾、MySQL女轿、Security、SpringSecurityOAuth2壕翩、Druid等蛉迹,項(xiàng)目結(jié)構(gòu)如下圖6所示:

圖6

項(xiàng)目構(gòu)建完成后我們要配置數(shù)據(jù)庫(kù)表結(jié)構(gòu),因?yàn)槲覀円菙?shù)據(jù)庫(kù)內(nèi)保存AccessToken以及RefershToken還有我們的SpringSecurity用戶驗(yàn)證信息以及用戶角色信息等放妈。

配置數(shù)據(jù)庫(kù)

安全用戶信息表

用戶信息表包含了簡(jiǎn)單的登錄名北救、密碼荐操、郵箱、狀態(tài)等珍策。表結(jié)構(gòu)如下圖7所示:

圖7

安全角色信息表

角色信息表結(jié)構(gòu)如下圖8所示:

圖8

用戶角色關(guān)聯(lián)表

用戶與角色關(guān)聯(lián)表結(jié)構(gòu)如下圖9所示:

圖9

AccessToken信息表

我們使用的是SpringSecurityOAuth2提供的Jdbc方式進(jìn)行操作Token托启,所以需要根據(jù)標(biāo)準(zhǔn)創(chuàng)建對(duì)應(yīng)的表結(jié)構(gòu),access_token信息表結(jié)構(gòu)如下圖10所示:

圖10

RefreshToken信息表

刷新Token時(shí)需要用到refresh_token信息表結(jié)構(gòu)如下圖11所示:

圖11

我們的數(shù)據(jù)庫(kù)表結(jié)構(gòu)已經(jīng)建完了膛壹,下面我們只需要?jiǎng)?chuàng)建用戶信息驾中、角色信息的實(shí)體即可,因?yàn)镺Auth2內(nèi)部操作數(shù)據(jù)庫(kù)使用的JdbcTemplate我們只需要傳入一個(gè)DataSource對(duì)象就可以了模聋,實(shí)體并不需要配置肩民。

創(chuàng)建用戶實(shí)體

用戶實(shí)體如下圖12所示:

圖12

創(chuàng)建角色實(shí)體

角色實(shí)體如下圖13所示:

圖13

用戶實(shí)體以及角色實(shí)體是用來(lái)配置SpringSecurity時(shí)用到的實(shí)體,我們配置SpringSecurity時(shí)需要使用SpringDataJPA從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)链方,下我們來(lái)配置UserJPA以及AuthorityJPA持痰。

UserJPA

配置訪問(wèn)數(shù)據(jù)庫(kù)獲取用戶信息,代碼如下圖14所示:

圖14

我們?cè)赨serJPA內(nèi)添加了一個(gè)自定義查詢祟蚀,使用了HQL語(yǔ)法來(lái)構(gòu)建的語(yǔ)句工窍,根據(jù)用戶名不區(qū)分大小寫(xiě)進(jìn)行查詢。

Application.yml配置文件

我們從之前的項(xiàng)目中第十三章:SpringBoot實(shí)戰(zhàn)SpringDataJPA中源碼復(fù)制一個(gè)application.yml配置文件到項(xiàng)目resources下(注意:需要修改對(duì)應(yīng)的數(shù)據(jù)庫(kù)配置)前酿,如下圖所示:

.

AuthorityJPA

配置訪問(wèn)數(shù)據(jù)庫(kù)中的角色列表患雏,代碼如下圖15所示:

圖15

下面我們來(lái)配置兩個(gè)控制器用來(lái)區(qū)分我們配置OAuth2是否已經(jīng)生效。

HelloWorldController

我在HelloWorldController內(nèi)只添加一個(gè)字符串的輸出罢维,這個(gè)控制器我們開(kāi)放淹仑,讓SpringSecurity不去管理,配置將會(huì)在下面展現(xiàn)肺孵,控制器代碼如下圖16所示:

圖16

SecureController

這個(gè)控制器是需要我們獲取授權(quán)Token后使用Token才可以訪問(wèn)到的匀借,代碼如下圖17所示:

圖17

綜上所述我們的項(xiàng)目基礎(chǔ)的構(gòu)建已經(jīng)完成,大家都知道SpringSecurity在使用數(shù)據(jù)庫(kù)的數(shù)據(jù)時(shí)需要自定義UserDetailsService用來(lái)從數(shù)據(jù)庫(kù)中根據(jù)用戶名查詢用戶信息以及角色信息并返回給SpringSecurity存放到內(nèi)存中平窘。

自定義UserDetailsService

我們創(chuàng)建一個(gè)名叫HengYuUserDetailsService的類并且實(shí)現(xiàn)UserDetailsService接口吓肋,代碼如下圖18所示:

圖18

我們?cè)贖engYuUserDetailsService類中做了從數(shù)據(jù)庫(kù)讀取用戶的操作,如果沒(méi)有查詢到用戶直接拋出異常提示瑰艘,如果查詢到并且設(shè)置對(duì)應(yīng)的角色后返回SpringSecurity內(nèi)置的User對(duì)象實(shí)例是鬼。

開(kāi)啟SpringSecurity配置

下面我們來(lái)配置SpringSecurity相關(guān)的內(nèi)容,我們新創(chuàng)建一個(gè)配置類SecurityConfiguration磅叛,代碼如下圖19所示:

圖19

我們?cè)谂渲妙愔凶⑷肓松厦嫖覀冏远x的HengYuUserDetailsService以及用戶密碼驗(yàn)證規(guī)則屑咳,我們使用ignoring()方法排除了HelloWorldController內(nèi)的公開(kāi)方法,這里可以配置通配符的形式排除弊琴。

配置安全資源服務(wù)器

下面我們開(kāi)始配置相關(guān)OAuth2的內(nèi)容兆龙,我們創(chuàng)建一個(gè)OAuth2總配置類OAuth2Configuration,類內(nèi)添加一個(gè)子類用于配置資源服務(wù)器,如下圖20所示:

圖20

我們?cè)贠Auth2Configuration配置類中添加子類ResourceServerConfiguration繼承自ResourceServerConfigurerAdapter完成資源服務(wù)器的配置紫皇,使用@EnableResourceServer注解來(lái)開(kāi)啟資源服務(wù)器慰安,因?yàn)檎蟂pringSecurity的緣故,我們需要配置登出時(shí)清空對(duì)應(yīng)的access_token控制以及自定義401錯(cuò)誤內(nèi)容(authenticationEntryPoint)聪铺,在配置類中我們排除了對(duì)/hello公開(kāi)地址攔截以及/secure下的所有地址都必須授權(quán)才可以訪問(wèn)化焕。

自定義401錯(cuò)誤碼內(nèi)容

我們上圖已經(jīng)用到了對(duì)應(yīng)的類CustomAuthenticationEntryPoint,該類是用來(lái)配置如果沒(méi)有權(quán)限訪問(wèn)接口時(shí)我們返回的錯(cuò)誤碼以及錯(cuò)誤內(nèi)容铃剔,代碼如下圖21所示:

圖21

定義登出控制

當(dāng)我們退出系統(tǒng)時(shí)需要訪問(wèn)SpringSecrutiy的logout方法來(lái)清空對(duì)應(yīng)的session信息撒桨,那我們退出后改用戶的access_token還依然存在那就危險(xiǎn)了,一旦別人知道該token就可以使用之前登錄用戶的權(quán)限來(lái)操作業(yè)務(wù)键兜。logout控制代碼如下圖22所示:

圖22

開(kāi)啟OAuth2驗(yàn)證服務(wù)器

我們還是在OAuth2Configuration配置類中添加一個(gè)子類凤类,用于開(kāi)啟OAuth2的驗(yàn)證服務(wù)器,代碼如下圖23普气、24所示:

圖23

圖23中我們創(chuàng)建了一個(gè)名叫AuthorizationServerConfiguration的類繼承自AuthorizationServerConfigurerAdapter并且實(shí)現(xiàn)了EnvironmentAware(讀取properties文件需要)接口谜疤,并使用@EnableAuthorizationServer注解開(kāi)啟了驗(yàn)證服務(wù)器,可以看到我們使用SpringSecurityOAuth2內(nèi)定義的JdbcStore來(lái)操作數(shù)據(jù)庫(kù)中的Token现诀,當(dāng)然需要有需要我們可以通過(guò)SpringDataJPA自定義Sotre夷磕。

圖24

圖24中我們的OAuth2的客戶端配置并沒(méi)有從數(shù)據(jù)庫(kù)中讀取而是使用了內(nèi)存中獲取,因?yàn)楸菊碌膬?nèi)容比較多仔沿,所以在后期文章中我們會(huì)再次講到如何從數(shù)據(jù)庫(kù)中獲取clients進(jìn)行驗(yàn)證坐桩。我們?cè)趧?chuàng)建客戶端信息時(shí)使用到了application.properties配置文件的自定義配置,具體配置內(nèi)容如下圖25所示:

圖25

運(yùn)行測(cè)試

項(xiàng)目編寫(xiě)完成封锉,接下來(lái)我們使用SpringBootApplication形式來(lái)運(yùn)行項(xiàng)目進(jìn)行測(cè)試撕攒,運(yùn)行項(xiàng)目時(shí)查詢控制臺(tái)輸出日志是否正確!

我們先來(lái)使用Postman工具訪問(wèn)一下我們公開(kāi)的地址127.0.0.1:8080/hello烘浦,如下圖26所示:

圖26

可以看到我們是可以正確的訪問(wèn)到接口輸出內(nèi)容的,下面我們?cè)賮?lái)訪問(wèn)一下被oauth2管理的地址127.0.0.1:8080/secure萍鲸,如下圖27所示:

圖27

我們可以看到直接給我們返回了一個(gè)頁(yè)面闷叉,這樣就不對(duì)了,我們應(yīng)該得到一個(gè)401的錯(cuò)誤碼以及自定義的信息才對(duì)脊阴,當(dāng)然我們需要添加一些配置來(lái)完成這個(gè)功能握侧,我們打開(kāi)application.properties配置文件添加如下圖28配置:

圖28

圖中畫(huà)紅色框的就是我們新添加的配置內(nèi)容,這個(gè)配置的意思時(shí)嘿期,將我們的資源攔截的過(guò)濾器運(yùn)行順序放到第3個(gè)執(zhí)行品擎,也就是在oauth2的認(rèn)證服務(wù)器后面執(zhí)行,我們重啟下項(xiàng)目再來(lái)訪問(wèn)下剛才的地址备徐,輸出內(nèi)容如下圖29所示:

圖29

可以看到正如我們預(yù)期一樣萄传,返回了401錯(cuò)誤以及我們自定義的錯(cuò)誤碼”Access Denied“,下面我們來(lái)獲取access_token蜜猾。

獲取AccessToken

我們?cè)讷@取token之前需要在數(shù)據(jù)庫(kù)中添加幾條對(duì)應(yīng)的數(shù)據(jù)秀菱,具體的SQL我會(huì)放到源碼項(xiàng)目的resources目錄下振诬,文章地址有源碼地址。我們來(lái)訪問(wèn)/oauth/token地址獲取access_token衍菱,如下圖30所示:

圖30

可以看到我們?cè)L問(wèn)的地址赶么,grant_type使用到了password模式,我們?cè)谏厦娴呐渲弥芯褪桥渲梦覀兊目蛻舳耍▂uqiyu_home_pc)可以執(zhí)行的模式有兩種:password脊串、refresh_token辫呻。獲取access_token需要添加客戶端的授權(quán)信息clientid、secret琼锋,通過(guò)Postman工具的頭授權(quán)信息即可輸出對(duì)應(yīng)的值就可以完成Basic Auth的加密串生成放闺。

成功訪問(wèn)后oauth2給我們返回了幾個(gè)參數(shù):

access_token:本地訪問(wèn)獲取到的access_token,會(huì)自動(dòng)寫(xiě)入到數(shù)據(jù)庫(kù)中斩例。
token_type:獲取到的access_token的授權(quán)方式
refersh_token:刷新token時(shí)所用到的授權(quán)token
expires_in:有效期(從獲取開(kāi)始計(jì)時(shí)雄人,值秒后過(guò)期)
scope:客戶端的接口操作權(quán)限(read:讀,write:寫(xiě))

使用AccessToken訪問(wèn)

我們使用獲取到的access_token值來(lái)訪問(wèn)對(duì)應(yīng)的地址http://127.0.0.1:8080/secure?access_token=9ca7fd9b-1289-440b-b1a1-0303782f660e念赶,效果如下圖31所示:

圖31

可以看到我們已經(jīng)可以正常的訪問(wèn)到數(shù)據(jù)內(nèi)容了础钠,證明我們的access_token是有效的。當(dāng)我們用到的token已經(jīng)過(guò)期時(shí)效果如下圖32所示:

圖32

oauth2告訴我們需要刷新Token了叉谜,您傳入的token值已經(jīng)過(guò)期了旗吁。

刷新AccessToken

我們的access_token過(guò)期我們需要刷新后返回新的token,使用新token才能繼續(xù)操作數(shù)據(jù)接口停局。刷新access_token如下圖33所示:

圖33

看到上圖33紅色框內(nèi)的值了嗎很钓?這個(gè)就是我們之前獲取token時(shí),oauth2給我們返回的refresh_token值董栽,我們需要用到該值來(lái)進(jìn)行刷新token码倦。新的token值得有效期可以看到又是我們配置的默認(rèn)1800秒,刷新token時(shí)oauth2還是給我們返回了一個(gè)refersh_token值锭碳,該值要作為下次刷新token時(shí)使用袁稽。

總結(jié)

綜上內(nèi)容就是本章的全部?jī)?nèi)容,本章的內(nèi)容比較多希望讀者可以仔細(xì)閱讀擒抛,本章主要講解了SpringBoot作為框架基礎(chǔ)上配置SpringSecurity安全框架整合OAuth2安全框架做雙重安全推汽,講解如果通過(guò)數(shù)據(jù)庫(kù)的形式獲取到授權(quán)用戶信息以及角色列表,通過(guò)內(nèi)存配置的OAuth2的客戶端配置來(lái)獲取access_token以及如何使用access_token訪問(wèn)受保護(hù)的資源接口歧沪。

本章代碼已經(jīng)上到碼云:

SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter

SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter

SpringBoot相關(guān)系列文章請(qǐng)?jiān)L問(wèn):目錄:SpringBoot學(xué)習(xí)目錄

QueryDSL相關(guān)系列文章請(qǐng)?jiān)L問(wèn):QueryDSL通用查詢框架學(xué)習(xí)目錄

SpringDataJPA相關(guān)系列文章請(qǐng)?jiān)L問(wèn):目錄:SpringDataJPA學(xué)習(xí)目錄

SpringBoot相關(guān)文章請(qǐng)?jiān)L問(wèn):目錄:SpringBoot學(xué)習(xí)目錄歹撒,感謝閱讀!

歡迎微信掃碼加入知識(shí)星球诊胞,恒宇少年帶你走以后的技術(shù)道路E病!!

知識(shí)星球 - 恒宇少年

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鳞尔,一起剝皮案震驚了整個(gè)濱河市嬉橙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寥假,老刑警劉巖市框,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異糕韧,居然都是意外死亡枫振,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)萤彩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)粪滤,“玉大人,你說(shuō)我怎么就攤上這事雀扶≌刃。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵愚墓,是天一觀的道長(zhǎng)予权。 經(jīng)常有香客問(wèn)我,道長(zhǎng)浪册,這世上最難降的妖魔是什么扫腺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮村象,結(jié)果婚禮上笆环,老公的妹妹穿的比我還像新娘。我一直安慰自己厚者,他們只是感情好躁劣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著库菲,像睡著了一般习绢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝙昙,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音梧却,去河邊找鬼奇颠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛放航,可吹牛的內(nèi)容都是我干的烈拒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼荆几!你這毒婦竟也來(lái)了吓妆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吨铸,失蹤者是張志新(化名)和其女友劉穎行拢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體诞吱,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舟奠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了房维。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沼瘫。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咙俩,靈堂內(nèi)的尸體忽然破棺而出耿戚,到底是詐尸還是另有隱情,我是刑警寧澤阿趁,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布膜蛔,位于F島的核電站,受9級(jí)特大地震影響歌焦,放射性物質(zhì)發(fā)生泄漏飞几。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一独撇、第九天 我趴在偏房一處隱蔽的房頂上張望屑墨。 院中可真熱鬧,春花似錦纷铣、人聲如沸卵史。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)以躯。三九已至,卻和暖如春啄踊,著一層夾襖步出監(jiān)牢的瞬間忧设,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工颠通, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留址晕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓顿锰,卻偏偏與公主長(zhǎng)得像谨垃,于是被迫代替她去往敵國(guó)和親启搂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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