OAuth2.0原理浅析

一、OAuth2.0是什么?

在OAuth2.0中“O”是Open的简称,表示“开放”的意思。Auth表示“授权”的意思,所以连起来OAuth表示“开放授权”的意思,它是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用。用一句话总结来说,OAuth2.0是一种授权协议。

OAuth允许用户授权第三方应用访问他存储在另外服务商里的各种信息数据,而这种授权不需要提供用户名和密码提供给第三方应用。

eg:万视达App通过微信三方登录方式登录,并且获取用户微信的昵称和头像等资料,这个过程万视达App平台并没有输入用户名和密码,而是跳转到微信输入微信账号、密码,待微信授权认证成功后,将用户在微信存储的信息提供给万视达,然后万视达App利用微信提供的信息注册登录万视达账号。

总的来说:OAuth2.0这种授权协议,就是保证第三方应用只有在获得授权之后,才可以进一步访问授权者的数据。

二、三方指的是哪三方?

  1. 第三方应用:要获取用户(资源拥有者)存储在服务提供商里的资源的实例,通常是客户端,这里我们的万视达App就是第三方应用;
  2. 服务提供者:存储用户(资源拥有者)的资源信息的地方,向第三方应用提供用户相关信息的服务方,例如三方登录时的微信、QQ;
  3. 用户/资源拥有者:拿三方登录来说,指的是在微信或QQ中注册的用户;
三方关系图

三、OAuth2.0的作用

1、解决的问题
  • 用户登录应用时传统的方式是用户直接进入客户端应用的登录页面输入账号和密码,但有的用户感觉注册后再登录比较繁琐麻烦,于是用户选择使用相关社交账号(微信、QQ、微博)登录,这种方式通过用户的账号和密码去相关社交账号里获取数据存在严重的问题缺陷。

    1. 如果第三方应用获取微信中的用户信息,那么你就把你的微信的账号和密码给第三应用。稍微有些安全意识,都不会这样做,这样很不安全;

      因此使用OAuth2.0可以避免向第三方暴露账号密码;

    2. 第三方应用拥有了用户微信的所有权限,用户没法限制第三方应用获得授权的范围和有效期;

      因此OAuth2.0可以限制授权第三方应用获取微信部分功能,比如只可以获取用户信息,但不可以获取好友列表,有需求时再申请授权访问好友列表的权限;

    3. 用户只有修改密码,才能收回赋予第三方应用权限,但是这样做会使得其他所有获得用户授权的第三方应用程序全部失效。

    4. 只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有使用微信登录的应用的数据泄漏。

  • 如果某个企业拥有多个应用系统平台,那么每个系统都需要设置一个账号密码,这种操作对于用户来说时繁琐麻烦的,没登录一个系统平台都要输入相应的账号密码,那么可不可以做一个平台,使任意用户可以在这个平台上注册了一个帐号以后,随后这个帐号和密码自动登记到这个平台中作为公共帐号,使用这个账号可以访问其它的已经授权访问的系统。在这种背景下,OAuth2.0协议就诞生了。

    OAuth的作用就是让"第三方应用"安全可控地获取"用户"的授权,与"服务商提供商"进行交互。本质是使用token令牌代替用户名密码。2、应用场景

2、项目应用场景

现在各大开放平台,如微信开放平台、腾讯开放平台、百度开放平台等大部分的开放平台都是使用的OAuth 2.0协议作为支撑

  1. 客户端App使用三方登录;
  2. 微信小程序登录授权;
  3. 多个服务的统一登录认证中心、内部系统之间受保护资源请求

四、OAuth2.0名词定义

  1. Third-party application:第三方应用客户端,本文中指的是万视达客户端
  2. HTTP service:服务提供商,本文中指的是微信
  3. Resource Owner:用户/资源拥有者,本文指的是在微信中注册的用户
  4. Authorization server:认证服务器,在资源拥有者授权后,向客户端授权(颁发 access token)的服务器
  5. Resource server:资源服务器,务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
  • 第三方应用的作用:

    扮演获取资源服务器数据信息的加色

  • 用户/资源所有者的作用:

    扮演只需要允许或拒绝第三方应用获得授权的角色

  • 授权认证服务器的作用:

    负责向第三方应用提供授权许可凭证code、令牌token等

  • 资源服务器的作用:

    提供给第三方应用注册接口,需要提供给第三方应用app_id和app_secret,提供给第三方应用开放资源的接口

五、OAuth2.0授权流程

1、OAuth的思路

OAuth在"第三方应用"与"服务提供商"之间,设置了一个授权层。"第三方应用"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"第三方应用"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。"第三方应用"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"第三方应用"开放用户储存的资料。

当用户登录了第三方应用后,会跳转到服务提供商获取一次性用户授权凭据),再跳转回来交给第三方应用,第三方应用的服务器会把授权凭据和服务提供商给它的身份凭据一起交给服务方,这样服务方既可以确定第三方应用得到了用户服务授权,又可以确定第三方应用的身份是可以信任的,最终第三方应用可以顺利的获取到服务商提供的web API的接口数据。

2、交互流程
image
  1. 用户打第三方开客户端后,第三方客户端要访问服务提供方,要求用户给予授权;
  2. 用户同意给予第三方客户端访问服务提供方的授权,并返回一个授权凭证Code;
  3. 第三方应用使用第2步获取的授权凭证Code和身份认证信息(appid、appsecret),向授权认证服务器申请授权令牌(token);
  4. 授权认证服务器验证三方客户端的授权凭证Code码和身份通过后,确认无误,同意授权,并返回一个资源访问的令牌(Access Token);
  5. 第三方客户端使用第4步获取的访问令牌Access Token)向资源服务器请求相关资源;
  6. 资源服务器验证访问令牌(Access Token)通过后,将第三方客户端请求的资源返回,同意向客户端开放资源;

下面是时序图:

image

六、OAuth2.0的授权模式

  1. 授权码模式(authorization code)
  2. 简化模式(implicit)
  3. 密码模式(resource owner password credentials)
  4. 客户端模式(client credentials)
1、授权码模式

第三方应用先申请获取一个授权码,然后再使用该授权码获取令牌,最后使用令牌获取资源;授权码模式是工能最完整、流程最严密的授权模式。它的特点是通过客户端的后台服务器,与服务提供商的认证服务器进行交互。

授权码模式

流程步骤

  • A [步骤1,2]:用户访问客户端,需要使用服务提供商(微信)的数据,触发客户端相关事件后,客户端拉起或重定向到服务提供商的页面或APP。
  • B [步骤3]:用户选择是否给予第三方客户端授权访问服务提供商(微信)数据的权限;
  • C [步骤4]:用户同意授权,授权认证服务器将授权凭证code码返回给客户端,并且会拉起应用或重定(redirect_uri)向到第三方网站;
  • D [步骤5,6]:客户端收到授权码后,将授权码code和ClientId或重定向URI发送给自己的服务器,客户端服务器再想认证服务器请求访问令牌access_token;
  • E [步骤7,8]:认证服务器核对了授权码和ClientId或重定向URI,确认无误后,向客户端服务器发送访问令牌(access_token)和更新令牌(refresh_token),然后客户端服务器再发送给客户端;
  • F [步骤9,10]:客户端持有access_token和需要请求的参数向客户端服务器发起资源请求,然后客户端服务器再向服务提供商的资源服务器请求资源(web API);
  • G [步骤11,12,13]:服务提供商的资源服务器返回数据给客户端服务器,然后再回传给客户端使用;

A步骤中客户端申请认证的URI,包含以下参数:

URL示例:

https://server.example.com/oauth/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read                

参数解释:

  • response_type:必选项,表示授权类型,此处的值固定为"code",表示使用授权码模式;
  • client_id:必选项,表示客户端的ID,第三方应用注册的id,身份认证;
  • redirect_uri:可选项,表示重定向URI,接受或拒绝请求后的跳转网址;
  • scope:可选项,表示申请的权限范围;
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值;

C步骤中,服务器回应客户端的URI,包含以下参数:

URL示例:

https:///server.example.com/callback?code=AUTHORIZATION_CODE&state=xyz

参数解释:

  • code:表示授权码,该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

URL示例:

https://server.example.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL

参数解释:

  • client_id:客户端id,第三方应用在服务提供者平台注册的;
  • client_secret:授权服务器的秘钥,第三方应用在服务提供者平台注册的;
  • grant_type:值是AUTHORIZATION_CODE,表示采用的授权方式是授权码;
  • code:表示上一步获得的授权码;
  • redirect_uri:redirect_uri参数是令牌颁发后的回调网址,且必须与A步骤中的该参数值保持一致;

注:client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,因此只能在后端发请求)

E步骤中,认证服务器发送的HTTP回复,包含以下参数:

服务提供者的平台收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据。

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101,
  "info":{...}
}

上面 JSON 数据中,access_token字段就是访问令牌。

2、简化模式

不需要获取授权码,第三方应用授权后授权认证服务器直接发送临牌给第三方应用,适用于静态网页应用,返回的access_token可见,access_token容易泄露且不可刷新。

简化模式不通过第三方应用程序的服务器,直接在客户端中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在客户端中完成,令牌对访问者是可见的,且客户端不需要认证。

简化模式

流程步骤

  • A [步骤1,2]:用户访问客户端,需要使用服务提供商(微信)的数据,触发客户端相关事件后,客户端拉起或重定向到服务提供商的页面或APP。
  • B [步骤3]:用户选择是否给予第三方客户端授权访问服务提供商(微信)数据的权限;
  • C [步骤4]:用户同意授权,授权认证服务器将访问令牌access_token返回给客户端,并且会拉起应用或重定(redirect_uri)向到第三方网站;
  • D[步骤5]:第三方客户端向资源服务器发出请求资源的请求;
  • E[步骤6,7]:服务提供商的资源服务器返回数据给客户端服务器,然后再回传给客户端使用;

A步骤中,客户端发出的HTTP请求,包含以下参数:

URL示例:

https://server.example.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

参数解释:

  • response_type:表示授权类型,此处的值固定为"token",表示要求直接返回令牌,必选项;
  • client_id:表示客户端的ID,第三方应用注册的id,用于身份认证,必选项;
  • redirect_uri:表示重定向URI,接受或拒绝请求后的跳转网址,可选项;
  • scope:表示申请的权限范围,可选项;
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值,可选项;

C步骤中,认证服务器回应客户端的URI,包含以下参数:

URL示例:

https://server.example.com/cb#access_token=ACCESS_TOKEN&state=xyz&token_type=example&expires_in=3600
或
https://a.com/callback#token=ACCESS_TOKEN

参数解释:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

注:注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

3、密码模式

如果你高度信任某个应用,允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"

使用用户名/密码作为授权方式从授权服务器上获取令牌,一般不支持刷新令牌。这种方式风险很大,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

密码模式

流程步骤

  • A[步骤1,2]:用户向第三方客户端提供,其在服务提供商那里注册的账户名和密码;
  • B[步骤3]:客户端将用户名和密码发给认证服务器,向后者请求令牌access_token;
  • C[步骤4]:授权认证服务器确认身份无误后,向客户端提供访问令牌access_token;

B步骤中,客户端发出的HTTP请求,包含以下参数:

URL示例:

https://oauth.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID

参数解释:

  • grant_type:标识授权方式,这里的password表示"密码式,必选项;
  • username: 表示用户名,必选项;
  • password:表示用户的密码,必选项;
  • scope:表示权限范围,可选项;

**认证服务器向客户端发送访问令牌,是一段 JSON 数据 **

{
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }
4、客户端模式

指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

客户端模式

主要是第2步和第3步:

  • 客户端向授权认证服务器进行身份认证,并申请访问临牌(token);
  • 授权认证服务器验证通过后,向客户端提供访问令牌。

A步骤中,客户端发出的HTTP请求,包含以下参数

URL示例:

https://oauth.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

参数解释:

  • grant_type:值为client_credentials表示采用凭证式(客户端模式)
  • client_id:客户端id,第三方应用在服务提供者平台注册的,用于身份认证;
  • client_secret:授权服务器的秘钥,第三方应用在服务提供者平台注册的,用于身份认证;

服务提供者验证通过以后,直接返回令牌。

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "example_parameter":"example_value"
}

七、令牌的使用和更新

令牌的使用

客户端拿到访问令牌后就可以通过web API向服务提供商的资源服务器请求资源了。

此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面。

curl -H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.example.com"
令牌的更新

令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许用户自动更新令牌。

具体方法是,服务提供商平台颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据的access_token,另一个用于获取新的令牌refresh_token。令牌到期前,用户使用 refresh_token 发一个请求,去更新令牌。

URL示例:

https://server.example.com/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN

参数解释:

  • grant_type: 参数为refresh_token表示要求更新令牌;
  • client_id:客户端id,第三方应用在服务提供者平台注册的,用于身份认证;
  • client_secret:授权服务器的秘钥,第三方应用在服务提供者平台注册的,用于身份认证;
  • refresh_token: 参数就是用于更新令牌的令牌

八、OAuth2.0和1.0的区别

OAuth2.0的最大改变就是不需要临时token了,直接authorize生成授权code,用code就可以换取access token了,同时access token加入过期,刷新机制,为了安全,要求第三方的授权接口必须是https的。OAuth2.0不能向下兼容OAuth1.0版本,OAuth2.0使用Https协议,OAuth1.0使用Http协议;

  • OAuth2.0去掉了签名,改用SSL确保安全性:OAuth1.0需要保证授权码和令牌token在传输的时候不被截取篡改,所以用了很多签名反复验证,但在OAuth2.0中是基于Https的。这里有个问题Https也有可能被中间人劫持;
  • 令牌刷新:OAuth2.0的访问令牌是“短命的”,且有刷新令牌(OAuth1.0可以存储一年及以上);
  • OAuth2.0所有的token不再有对应的secret存在,签名过程简洁,这也直接导致OAuth2.0不兼容老版本;
  • 对于redirect_uri的校验,OAuth1.0中没有提到redirect_uri的校验,那么OAuth2.0中要求进行redirect_uri的校验;
  • 授权流程区别:OAuth1.0授权流程太过单一,除了Web应用以外,对桌面、移动应用来说不够友好。而OAuth2.0提供了四中授权模式;

九、三方登录中OAuth2.0的使用

无论使用那种平台授权登录,都需要去相应的开放平台备案,申请appid、appsecret,来证明你的身份,第三方持凭此就有资格去请求该平台的接口。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。

注:目前移动应用上微信登录只提供原生的登录方式,需要用户安装微信客户端才能配合使用,通过通用链接(Universal Links)实现第三方应用与微信,QQ之间的跳转。

1、三方登录如何做到的呢?
  • 用户的授权信息在授权服认证务器中是有记录保存的,当用户第一次授权给第三方用用后,以后不需要再次授权;
  • 授权令牌(token)是有实效性的, access_token 超时后,可以使用 refresh_token 进行刷新,refresh_token 拥有较长的有效期(30 天),当 refresh_token 失效的后,需要用户重新授权;
  • 每个用户在资源服务器中都有一个唯一的ID,第三方应用可以将其存储起来并与本地用户系统一一对应起来;
2、微信三方登录OAuth2.0获取access_token流程
  • 微信 OAuth2.0 授权登录让微信用户使用微信身份安全登录第三方应用或网站,在微信用户授权登录已接入微信 OAuth2.0 的第三方应用后,第三方可以获取到用户的接口调用凭证(access_token),通过 access_token 可以进行微信开放平台授权关系接口调用,从而可实现获取微信用户基本开放信息和帮助用户实现基础开放功能等。

    微信 OAuth2.0 授权登录目前支持 authorization_code 模式,适用于拥有 server 端的应用授权。该模式整体流程为:

    1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;

    2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;

    3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

  • 获取 access_token 时序图:

获取 access_token 时序图
  • access_token刷新机制

    access_token 是调用授权关系接口的调用凭证,由于 access_token 有效期(目前为 2 个小时)较短,当 access_token 超时后,可以使用 refresh_token 进行刷新,access_token 刷新结果有两种:

    1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
    2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。

    refresh_token 拥有较长的有效期(30 天),当 refresh_token 失效的后,需要用户重新授权。

具体流程看微信开放平台文档

总结

总的来说OAuth就是一种授权机制,数据的所有者告诉系统,统一授权第三方应用进入系统,获取部分数据。系统产生短期有实效和权限范围的令牌(token)给第三方应用,用来代替密码,供第三方使用。OAuth2.0授权的核心就是颁发访问令牌、使用访问令牌。也可以认为OAuth2.0是一个安全协议,按照OAuth2.0的规范来实施,就可以用来保护互联网中受保护资源。

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容