Asp.net Web API 解決跨域詳解

本文翻譯自Enabling Cross-Origin Requests in ASP.NET Web API 2
瀏覽器安全防止web頁(yè)面發(fā)出AJAX請(qǐng)求到另一個(gè)領(lǐng)域。這種限制稱(chēng)為同源策略,這是為了防止惡意網(wǎng)站讀取敏感數(shù)據(jù)滤钱。然而,有時(shí)候与境。您可能想要讓其他網(wǎng)站調(diào)用您的web API。
Cross Origin Resource Sharing(CORS)是一種W3C標(biāo)準(zhǔn),允許服務(wù)器放松同源策略窘行。CROS,服務(wù)器可以允許一些跨域源而拒絕其他域的請(qǐng)求崭放。CORS比之前JSONP等技術(shù)更安全电禀、更靈活。本教程展示了如何在Web API的應(yīng)用程序中啟用CROS己莺。

介紹

本教程演示了ASP.NET Web API.中使用 CORS奏甫。我們將首先創(chuàng)建兩個(gè)ASP.NET 項(xiàng)目。一個(gè)包含Web API控制器的“WebService”,另外一個(gè)其他“WebClient”,它調(diào)用WebService的接口凌受。因?yàn)閮蓚€(gè)應(yīng)用程序在不同的領(lǐng)域,一個(gè)AJAX請(qǐng)求從WebClient到WebService是一個(gè)跨源的要求阵子。

跨域請(qǐng)求流程

什么是同源

如果兩個(gè)URL他們有相同的域名,端口號(hào),這兩個(gè)URL就是有相同的源.即:同源
判斷是否同源有三個(gè)要素胜蛉,我們暫且稱(chēng)它們?yōu)椤巴慈亍保簠f(xié)議挠进,域名,端口號(hào)誊册。只要三要素中任何一個(gè)不一樣领突,就不同源。
下面是同源的兩個(gè)URL

  • 1 http://example.com/foo.htm
  • 2 http://example.com/bar.html
    下面幾個(gè)URL相比上面兩個(gè)URL是不同源的
  • 1 http://example.net//域名不一樣
  • 2 http://example.com:9000/foo.html//端口號(hào)不一樣
  • 3 https://example.com/foo.html//協(xié)議不一樣案怯,采用了Https
  • 4 https://www.example.com/foo.html
創(chuàng)建WebService 項(xiàng)目
image.png

添加一個(gè) 名為 TestControllerWeb API 控制器

using System.Net.Http;
using System.Web.Http;

namespace WebService.Controllers
{
    public class TestController : ApiController
    {
        public HttpResponseMessage Get()
        {
            return new HttpResponseMessage()
            {
                Content = new StringContent("GET: Test message")
            };
        }

        public HttpResponseMessage Post()
        {
            return new HttpResponseMessage()
            {
                Content = new StringContent("POST: Test message")
            };
        }

        public HttpResponseMessage Put()
        {
            return new HttpResponseMessage()
            {
                Content = new StringContent("PUT: Test message")
            };
        }
    }
}

你可以在本地運(yùn)行應(yīng)用程序或部署到Azure君旦。(本教程中的截圖,我Web應(yīng)用程序部署到Azure應(yīng)用服務(wù)。)驗(yàn)證web API是否啟動(dòng)成功,導(dǎo)航到http://hostname/api/test/,主機(jī)名是署應(yīng)用程序時(shí)使用的域名嘲碱。您應(yīng)該看到響應(yīng)報(bào)文,“GET: Test Message”金砍。

image.png

創(chuàng)建WebClient 項(xiàng)目

image.png

在解決方案資源管理器,打開(kāi)文件/ Home / Index.cshtml 。用以下代碼替換該文件中的代碼:

<div>
    <select id="method">
        <option value="get">GET</option>
        <option value="post">POST</option>
        <option value="put">PUT</option>
    </select>
    <input type="button" value="Try it" onclick="sendRequest()" />
    <span id='value1'>(Result)</span>
</div>

@section scripts {
<script>
    // TODO: Replace with the URL of your WebService app
    var serviceUrl = 'http://mywebservice/api/test'; 

    function sendRequest() {
        var method = $('#method').val();

        $.ajax({
            type: method,
            url: serviceUrl
        }).done(function (data) {
            $('#value1').text(data);
        }).error(function (jqXHR, textStatus, errorThrown) {
            $('#value1').text(jqXHR.responseText || textStatus);
        });
    }
</script>
}

備注:serviceUrl變量,使用WebService 項(xiàng)目的URI÷缶猓現(xiàn)在在本地運(yùn)行WebClient應(yīng)用程序或發(fā)布到另一個(gè)網(wǎng)站捞魁。

點(diǎn)擊"Try It”按鈕提交一個(gè)AJAX請(qǐng)求到WebService應(yīng)用程序,使用下拉框中列出的HTTP方法(GET、POST离咐、或者put)谱俭。這讓我們檢查不同跨源請(qǐng)求。現(xiàn)在, WebService應(yīng)用程序不支持CORS,所以如果你單擊按鈕,你會(huì)得到一個(gè)錯(cuò)誤宵蛀。


image.png

允許CORS

現(xiàn)在讓我們?cè)赪ebService應(yīng)用CORS昆著。首先,添加CORSNuGet包。在Visual Studio中,從“工具”菜單上,選擇庫(kù)軟件包管理器,然后選擇包管理器控制臺(tái)术陶。在包管理器控制臺(tái)窗口中,鍵入以下命令:
nstall-Package Microsoft.AspNet.WebApi.Cors
這個(gè)命令安裝最新的包和更新所有依賴(lài)項(xiàng),包括核心Web API庫(kù)凑懂。User version標(biāo)志針對(duì)一個(gè)特定的版本。 CORS包需要Web API 2.0或更高版本梧宫。
打開(kāi)文件App_Start / WebApiConfig.cs接谨。將下面的代碼添加到WebApiConfig.Register方法摆碉。

using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

接下來(lái),TestController類(lèi)添加EnableCors屬性:

using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        // Controller methods not shown...
    }
}

origins參數(shù),使用WebClient應(yīng)用程序部署時(shí)用用的的URI,這允許跨域源來(lái)自WebClient的請(qǐng)求,同時(shí)還禁止所有其他跨域請(qǐng)求脓豪。之后,我將詳細(xì)描述[EnableCors]的參數(shù)巷帝。

備注:URI與URL不同,URI :Uniform Resource Identifier扫夜,統(tǒng)一資源標(biāo)識(shí)符
URL:Uniform Resource Locator楞泼,統(tǒng)一資源定位符
URI以scheme和冒號(hào)開(kāi)頭。Scheme用大寫(xiě)/小寫(xiě)字母開(kāi)頭笤闯,后面為空或者跟著更多的大寫(xiě)/小寫(xiě)字母堕阔、數(shù)字、加號(hào)颗味、減號(hào)和點(diǎn)號(hào)超陆。冒號(hào)把scheme與scheme-specific-part分開(kāi)了,并且scheme-specific-part的語(yǔ)法和語(yǔ)義(意思)由URI的名字空間決定浦马。如下面的例子:
http://域名侥猬,其中http是scheme,//域名 是scheme-specific-part捐韩,并且它的scheme與scheme-specific-part被冒號(hào)分開(kāi)了退唠。

重新部署更新WebService 的應(yīng)用程序。你不需要更新WebClient』缧玻現(xiàn)在WebClient的AJAX請(qǐng)求應(yīng)該成功瞧预。GET、PUT和POST方法都是允許的仅政。


image.png

CORS工作原理

本節(jié)描述了在Http協(xié)議標(biāo)準(zhǔn)上http跨域請(qǐng)求中究竟發(fā)生了什么垢油。重要的是要理解CORS是如何工作的,這樣你就可以正確配置[EnableCors]屬性,和如果CORS不像您預(yù)期的那樣工作怎樣排除錯(cuò)誤。
CORS為了允許使跨源請(qǐng)求引入了幾個(gè)新的HTTP頭圆丹。如果瀏覽器支持CORS,它自動(dòng)設(shè)置這些請(qǐng)求頭滩愁,你不需要在你的JavaScript代碼做任何修改。

這是一個(gè)跨域請(qǐng)求的例子辫封。Origin請(qǐng)求頭提供了產(chǎn)生這個(gè)跨域請(qǐng)求的網(wǎng)站域名硝枉。

請(qǐng)求報(bào)文

如果服務(wù)器允許這個(gè)跨域請(qǐng)求,響應(yīng)報(bào)文中自動(dòng)設(shè)置Access-Control-Allow-Origin頭倦微。這個(gè)頭的值匹配請(qǐng)求報(bào)文中Origin頭的值,或者是通配符“*”,這意味著任何起源是被允許的妻味。


響應(yīng)報(bào)文

如果響應(yīng)不包括Access-Control-Allow-Origin頭,這是AJAX請(qǐng)求失敗。具體來(lái)說(shuō)是瀏覽器不允許請(qǐng)求欣福。即使服務(wù)器返回一個(gè)成功的響應(yīng),瀏覽器響應(yīng)的結(jié)果不可用于客戶(hù)端應(yīng)用程序责球。

CORS 預(yù)檢請(qǐng)求preflight request

對(duì)于一些CORS請(qǐng)求,瀏覽器會(huì)發(fā)送一個(gè)額外的請(qǐng)求,稱(chēng)為預(yù)檢請(qǐng)求“preflight request”,在發(fā)送的實(shí)際請(qǐng)求的資源之前。
瀏覽器可以跳過(guò)preflight request如果下列條件屬實(shí):

  • 1 請(qǐng)求的方法是GET, HEAD, or POST,等
  • 2 應(yīng)用程序不設(shè)置任何請(qǐng)求頭除了Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID等
  • 3 content - type報(bào)頭(如果設(shè)置)是下列之一:
    application/x-www-form-urlencoded
    multipart/form-data
    text/plain
    這個(gè)請(qǐng)求頭的規(guī)范適用于當(dāng)應(yīng)用程序調(diào)用setRequestHeade XMLHttpRequest對(duì)象時(shí)發(fā)起的請(qǐng)求頭。 規(guī)范并不適用于瀏覽器的請(qǐng)求頭可以設(shè)置,如用戶(hù)代理,主機(jī),或內(nèi)容長(zhǎng)度雏逾。

下面是preflight request的一個(gè)例子

image.png

pre-flight請(qǐng)求使用HTTP OPTIONS方法嘉裤。它包括兩個(gè)特殊的請(qǐng)求頭:
Access-Control-Request-Method:HTTP方法將被用于實(shí)際的請(qǐng)求。
Access-Control-Request-Headers:應(yīng)用程序設(shè)置的實(shí)際的請(qǐng)求頭的列表栖博。(同樣,這并不包括瀏覽器設(shè)置的請(qǐng)求頭屑宠。)
這里有一個(gè)響應(yīng)報(bào)文例子,假設(shè)服務(wù)器允許請(qǐng)求:


image.png

響應(yīng)包含一個(gè)Access-Control-Allow-Methods列出允許的方法、和可選一個(gè)Access-Control-Allow-Headers頭列表允許的頭笛匙。如果preflight請(qǐng)求成功,瀏覽器發(fā)送實(shí)際的請(qǐng)求,如前所述。

[EnableCors]設(shè)置

您可以啟用 CORS在每一個(gè) action,controller,或Web API全局控制器中犀变。

  • 1 action設(shè)置
    在action上允許跨域妹孙,設(shè)置[EnableCors]屬性的action方法。下面的例子使GetItemmethod 單獨(dú)允許跨域
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }

    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}
  • 2 controller設(shè)置
    如果您設(shè)置EnableCors在控制器,它適用于該控制器上的所有的action获枝。如果想對(duì)某一個(gè)action禁用跨域蠢正,請(qǐng)使用[DisableCors]特性。下面的例子除了PutItem action 其他action都支持跨域省店。
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }

    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}
  • 3 全局設(shè)置
    在應(yīng)用程序中為所有Web API 控制器允許跨域,將一個(gè)EnableCorsAttribute實(shí)例傳遞給EnableCors方法:
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

如果你在多個(gè)范圍內(nèi)多個(gè)設(shè)置[EnableCors]嚣崭、優(yōu)先順序是:
1Action
2Controller
3Global

[EnableCors]參數(shù)origin介紹

[EnableCors]的origins 參數(shù)指定了哪一個(gè)請(qǐng)求起源是允許訪問(wèn)的。允許的值之間是一個(gè)以逗號(hào)分隔的懦傍。

[EnableCors(origins: "http://www.contoso.com,http://www.example.com", 
    headers: "*", methods: "*")]

[EnableCors]參數(shù)methods介紹

[EnableCors]特性的methods雹舀,指定了哪一個(gè)HTTP方法可以訪問(wèn)資源。為了使所有方法都可以訪問(wèn)粗俱,使用通配符“ * ”说榆。下面是一個(gè)只允許GET和POST方法的請(qǐng)求示例:

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage Put() { ... }    
}

[EnableCors]參數(shù)headers介紹

[EnableCors]特性的headers,指定了哪一個(gè)HTTP請(qǐng)求頭可以訪問(wèn)資源寸认。為了使任何請(qǐng)求頭都可以訪問(wèn)签财,使用通配符“ * ”,多個(gè)允許的headers之間使用一個(gè)逗號(hào)來(lái)分隔偏塞。

   [EnableCors(origins: "http://example.com", 
    headers: "accept,content-type,origin,x-my-header", methods: "*")]

設(shè)置允許響應(yīng)標(biāo)頭

默認(rèn)情況下,瀏覽器不公開(kāi)所有的應(yīng)用程序響應(yīng)標(biāo)頭唱蒸。可用的響應(yīng)頭默認(rèn)情況下是:

Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma

CORS 規(guī)定了調(diào)用這些簡(jiǎn)單的響應(yīng)頭灸叼。于應(yīng)用程序中使用其他頭文件,請(qǐng)?jiān)O(shè)置[EnableCors]的exposedHeaders參數(shù)
在接下來(lái)的例子中,控制器的Get方法設(shè)置一個(gè)自定義標(biāo)頭命名為“X-Custom-Header”神汹。默認(rèn)情況下,瀏覽器不會(huì)在跨源的請(qǐng)求中暴露這個(gè)自定義標(biāo)頭。為了使自定義標(biāo)頭有效,使 exposedHeaders 參數(shù)的值為X-Custom-Header”古今。

[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var resp = new HttpResponseMessage()
        {
            Content = new StringContent("GET: Test message")
        };
        resp.Headers.Add("X-Custom-Header", "hello");
        return resp;
    }
}

在跨域請(qǐng)求中通過(guò)證書(shū)請(qǐng)求

跨域請(qǐng)求中使用證書(shū)需要特殊處理慎冤。默認(rèn)情況下,瀏覽器不發(fā)送任何證書(shū)憑證與跨源的要求。憑證不但包括cookies還包括HTTP身份驗(yàn)證方案沧卢。為了在跨源請(qǐng)求發(fā)送憑證,客戶(hù)端必須設(shè)置XMLHttpRequest.withCredentials為true蚁堤。

  • c#
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;
  • JS
$.ajax({
    type: 'get',
    url: 'http://www.example.com/api/test',
    xhrFields: {
        withCredentials: true
    }

此外,服務(wù)器必須允許憑據(jù)。在Web API,(EnableCors)特性的允許跨源憑證SupportsCredentials參數(shù)設(shè)置為true

[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", 
    methods: "*", SupportsCredentials = true)]

如果這個(gè)屬性是true,HTTP響應(yīng)將包含Access-Control-Allow-Credentials頭。這個(gè)頭告訴瀏覽器跨源請(qǐng)求的服務(wù)器允許憑據(jù)披诗。
如果瀏覽器發(fā)送證書(shū),但是響應(yīng)不包括一個(gè)有效的Access-Control-Allow-Credentials頭,瀏覽器不會(huì)公開(kāi)響應(yīng)應(yīng)用程序,并且AJAX請(qǐng)求失敗撬即。
務(wù)必小心將SupportsCredentials設(shè)置為true,因?yàn)檫@意味在一個(gè)網(wǎng)站在另一個(gè)域可以發(fā)送一個(gè)登錄的用戶(hù)的憑證代表用戶(hù)的Web API,。CORS還規(guī)定,設(shè)置“*”的Origin是無(wú)效的呈队,在SupportsCredentials是true的情況下剥槐。

自定義[EnableCors]特性

[EnableCors]特性實(shí)現(xiàn)了ICorsPolicyProvider接口。您可以提供自己的實(shí)現(xiàn)通過(guò)創(chuàng)建一個(gè)類(lèi),它來(lái)繼承Attribute和實(shí)現(xiàn)了ICorsProlicyProvider接口宪摧。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
{
    private CorsPolicy _policy;

    public MyCorsPolicyAttribute()
    {
        // Create a CORS policy.
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // Add allowed origins.
        _policy.Origins.Add("http://myclient.azurewebsites.net");
        _policy.Origins.Add("http://www.contoso.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

現(xiàn)在你可以在你想要允許跨域的任何地方使用你剛才自定義的[EnableCors].特性

[MyCorsPolicy]
public class TestController : ApiController
{
    .. //
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粒竖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子几于,更是在濱河造成了極大的恐慌蕊苗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沿彭,死亡現(xiàn)場(chǎng)離奇詭異朽砰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)喉刘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)瞧柔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人睦裳,你說(shuō)我怎么就攤上這事造锅。” “怎么了廉邑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵备绽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鬓催,道長(zhǎng)肺素,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任宇驾,我火速辦了婚禮倍靡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘课舍。我一直安慰自己塌西,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布筝尾。 她就那樣靜靜地躺著捡需,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筹淫。 梳的紋絲不亂的頭發(fā)上站辉,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼饰剥。 笑死殊霞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汰蓉。 我是一名探鬼主播绷蹲,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼顾孽!你這毒婦竟也來(lái)了祝钢?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤若厚,失蹤者是張志新(化名)和其女友劉穎拦英,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盹沈,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡龄章,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年吃谣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乞封。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岗憋,死狀恐怖肃晚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仔戈,我是刑警寧澤关串,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站监徘,受9級(jí)特大地震影響晋修,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凰盔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一墓卦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧户敬,春花似錦落剪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至抄瑟,卻和暖如春凡泣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工问麸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留往衷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓严卖,卻偏偏與公主長(zhǎng)得像席舍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哮笆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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