nodejs鑒權(quán)

三種常見鑒權(quán)方式

  • Session/CookieToken
  • OAuth
  • SSO
session-cookie方式
//cookie原理解析
// cookie.js
const http = require("http")
http.createServer((req, res) => {
        if (req.url === '/favicon.ico') {
            res.end('')
            return
        }
        // 觀察cookie存在
        console.log('cookie:', req.headers.cookie) // 設(shè)置cookie
        res.setHeader('Set-Cookie', 'cookie1=abc;') 
        res.end('hello cookie!!')
    })
    .listen(3000)
set-cookie.png

由于cookie的明文傳輸概疆,而且前端很容易篡改臀突,不是很安全,另外cookie是有容量限制的,因此可以存儲一個編號嫉柴,編號對應(yīng)的內(nèi)容就可以放在服務(wù)器端。

const session = {}
//...
  if (req.url === '/favicon.ico') {
        res.end('')
        return
    }
    // 觀察cookie存在
    console.log('cookie:', req.headers.cookie) // 設(shè)置cookie
    const sessionKey = 'sid'
    const cookie = req.headers.cookie
    if (cookie && cookie.indexOf(sessionKey) > -1) {
        res.end('Come Back ')
        // 簡略寫法未必具有通用性
        const pattern = new RegExp(`${sessionKey}=([^;]+);?\s*`)
        const sid = pattern.exec(cookie)[1]
        console.log('session:', sid, session, session[sid])
    } else {
        const sid = (Math.random() * 99999999).toFixed()
        // 設(shè)置cookie
        res.setHeader('Set-Cookie', `${sessionKey}=${sid};`)
        session[sid] = { name: 'laowang' }
        res.end('Hello')
    }
//...

session會話機制是一種服務(wù)器端機制钾虐,它使用類似于哈希表(可能還有哈希表)的結(jié)構(gòu)來保存信息眼五。

原理
image.png

實現(xiàn)原理: 1. 服務(wù)器在接受客戶端首次訪問時在服務(wù)器端創(chuàng)建seesion,然后保存seesion(我們可以將 seesion保存在內(nèi)存中植康,也可以保存在redis中旷太,推薦使用后者),然后給這個session生成一 個唯一的標(biāo)識字符串,然后在響應(yīng)頭中種下這個唯一標(biāo)識字符串销睁。2. 簽名泳秀。這一步通過秘鑰對sid進行簽名處理,避免客戶端修改sid榄攀。(非必需步驟)3. 瀏覽器中收到請求響應(yīng)的時候會解析響應(yīng)頭嗜傅,然后將sid保存在本地cookie中,瀏覽器在下次http請求的請求頭中會帶上該域名下的cookie信息檩赢,4. 服務(wù)器在接受客戶端請求時會去解析請求頭cookie中的sid吕嘀,然后根據(jù)這個sid去找服務(wù)器端保存的該客戶端的session违寞,然后判斷該請求是否合法。

koa中的session使用

koa是一個新的Web框架偶房,致力于成為Web應(yīng)用和api開發(fā)領(lǐng)域中的一個更小趁曼,更富有表現(xiàn)力,更健壯的基石棕洋,是express的下一代基于node.js的web框架 挡闰,完全使用Promise并配合async來實現(xiàn)異步。
特點: 輕量 無捆綁 中間件架構(gòu) 優(yōu)雅的api設(shè)計 增強錯誤處理
// 安裝: npm i koa koa-session -S

const Koa = require('koa')
const app = new Koa()
const session = require('koa-session')
// 簽名key keys作用 用來對cookie進行簽名 
app.keys = ['some secret'];
// 配置項
const SESS_CONFIG = {
    key: 'kkb:sess', // cookie鍵名 
    maxAge: 86400000, // 有效期掰盘,默認(rèn)一天 
    httpOnly: true, // 僅服務(wù)器修改 
    signed: true, // 簽名cookie
};
// 注冊
app.use(session(SESS_CONFIG, app));
// 測試 app.use(ctx => {
app.use(ctx => {
    if (ctx.path === '/favicon.ico') return; // 獲取
    let n = ctx.session.count || 0;
    // 設(shè)置
    ctx.session.count = ++n;
    ctx.body = '第' + n + '次訪問';
});
app.listen(3000)

訪問http://localhost:3000/

image.png

哈希Hash - SHA MD5
  • 把一個不定長摘要定長結(jié)果 -摘要 yanglaoshi -> x -雪崩效應(yīng)

使用聲明一個變量的方式存儲session的這種方式摄悯,實際上就是存儲在內(nèi)存中,當(dāng)用戶訪問量增大的時候愧捕,就會導(dǎo)致內(nèi)存暴漲奢驯,而且如果服務(wù)器關(guān)機,那么駐留在內(nèi)存中的session就會清空次绘,第三點是服務(wù)器采用多機器部署瘪阁,用戶不一定每次都會訪問到同一臺機器,基于這三種情況我們需要把session保存在一個公共的位置邮偎,不能保存在內(nèi)存中管跺,這時候我們想到使用redis。

使用redis存儲session

redis是一個高性能的key-value數(shù)據(jù)庫禾进,Redis 與其他 key - value 緩存產(chǎn)品有以下三個特點:

  • Redis支持?jǐn)?shù)據(jù)的持久化伙菜,可以將內(nèi)存中的數(shù)據(jù)保存在磁盤中,重啟的時候可以再次加載進行使用命迈。
  • Redis不僅僅支持簡單的key-value類型的數(shù)據(jù)贩绕,同時還提供list,set壶愤,zset淑倾,hash等數(shù)據(jù)結(jié)構(gòu)的存儲。
  • Redis支持?jǐn)?shù)據(jù)的備份征椒,即master-slave模式的數(shù)據(jù)備份娇哆。
優(yōu)勢
  • 性能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
  • 豐富的數(shù)據(jù)類型 – Redis支持二進制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 數(shù)據(jù)類型操作勃救。
  • 原子 – Redis的所有操作都是原子性的碍讨,意思就是要么成功執(zhí)行要么失敗完全不執(zhí)行。單個操作是原子性的蒙秒。多個操作也支持事務(wù)勃黍,即原子性,通過MULTI和EXEC指令包起來晕讲。
  • 豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過期等等特性覆获。
// npm install redis -S
// redis.js
const redis = require('redis');
const client = redis.createClient(6379, 'localhost');
client.set('hello', 'This is a value');
client.get('hello', function (err, v) {
    console.log("redis get ", v);
})
// koa-redis.js
const redisStore = require('koa-redis');
const redis = require('redis')
const redisClient = redis.createClient(6379, "localhost");
const wrapper = require('co-redis'); //為了在中間件中使用redisStore
const client = wrapper(redisClient);
app.use(session({
    key: 'kkb:sess',
    store: redisStore({ client }) // 此處可以不必指定client
}, app));
app.use(async (ctx, next) => {
    const keys = await client.keys('*')
    keys.forEach(async key =>
        console.log(await client.get(key))
    )
    await next()
})

為什么要將session存儲在外部存儲中,Session信息未加密存儲在客戶端cookie中瀏覽器cookie有長度限制

一個登錄鑒權(quán)驗證的小李子??

//index.js
const Koa = require('koa')
const router = require('koa-router')()
const session = require('koa-session')
const cors = require('koa2-cors')
const bodyParser = require('koa-bodyparser')
const static = require('koa-static')
const app = new Koa();

//配置session的中間件
app.use(cors({
    credentials: true
}))
app.keys = ['some secret'];

app.use(static(__dirname + '/'));
app.use(bodyParser())
app.use(session(app));

app.use((ctx, next) => {
    if (ctx.url.indexOf('login') > -1) {
        next()
    } else {
        console.log('session', ctx.session.userinfo)
        if (!ctx.session.userinfo) {
            ctx.body = {
                message: "登錄失敗"
            }
        } else {
            next()
        }
    }
})

router.post('/login', async (ctx) => {
    const {
        body
    } = ctx.request
    console.log('body',body)
    //設(shè)置session
    ctx.session.userinfo = body.username;
    ctx.body = {
        message: "登錄成功"
    }
})
router.post('/logout', async (ctx) => {
    //設(shè)置session
    delete ctx.session.userinfo
    ctx.body = {
        message: "登出系統(tǒng)"
    }
})
router.get('/getUser', async (ctx) => {
    ctx.body = {
        message: "獲取數(shù)據(jù)成功",
        userinfo: ctx.session.userinfo
    }
})

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
//index.html
<html>

<head>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
  <div id="app">
    <div>
      <input v-model="username">
      <input v-model="password">
    </div>
    <div>
      <button v-on:click="login">Login</button>
      <button v-on:click="logout">Logout</button>
      <button v-on:click="getUser">GetUser</button>
    </div>
    <div>
      <button onclick="document.getElementById('log').innerHTML = ''">Clear Log</button>
    </div>
  </div>
  <h6 id="log"></h6>
  </div>
  <script>
    // axios.defaults.baseURL = 'http://localhost:3000'
    axios.defaults.withCredentials = true
    axios.interceptors.response.use(
      response => {
        document.getElementById('log').append(JSON.stringify(response.data))
        return response;
      }
    );
    var app = new Vue({
      el: '#app',
      data: {
        username: 'test',
        password: 'test'
      },
      methods: {
        async login() {
          await axios.post('/login', {
            username: this.username,
            password: this.password
          })
        },
        async logout() {
          await axios.post('/logout')
        },
        async getUser() {
          await axios.get('/getUser')
        }
      }
    });
  </script>
</body>
</html>
login.png

利用session要求服務(wù)器本身要有狀態(tài)的马澈,這樣實現(xiàn)起來難度比較大的,最好是我們可以提供一種服務(wù)讓后端可以沒有狀態(tài)弄息,雖然我們現(xiàn)在使用redis加一個全局的狀態(tài)保持統(tǒng)一痊班,這樣比較適合通過分布式系統(tǒng)進行實現(xiàn),所以這是token產(chǎn)生的一個原因摹量,現(xiàn)在實際在前端應(yīng)用使用cookie-session的模式已經(jīng)很少了涤伐,更多的是使用token模式

token驗證
image

1.客戶端使用用戶名和密碼請求登錄
2.服務(wù)端收到請求,去驗證用戶名與密碼
3.驗證成功后缨称,服務(wù)端會簽發(fā)一個令牌(token) ,再把這個token發(fā)送給客戶端
4.客戶端收到token以后可以把它存儲起來凝果,比如放在cookie里或者local storage里
5.客戶端每次向服務(wù)端請求資源的時候需要帶著服務(wù)端簽發(fā)的token
6.服務(wù)端收到請求然后去驗證客戶端的請深圳市里面帶著的Token 如果驗證成功,就向客戶端返回請求的數(shù)據(jù)

const Koa = require('koa')
const router = require('koa-router')()
const static = require('koa-static')
const bodyParser = require('koa-bodyparser')
const app = new Koa();
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");

const secret = "it's a secret";
app.use(bodyParser())
app.use(static(__dirname + '/'));

router.post("/login-token", async ctx => {
  const { body } = ctx.request;
  //登錄邏輯具钥,略
  //設(shè)置session
  const userinfo = body.username;
  ctx.body = {
    message: "登錄成功",
    user: userinfo,
    // 生成 token 返回給客戶端
    token: jwt.sign(
      {
        data: userinfo,
        // 設(shè)置 token 過期時間,一小時后骂删,秒為單位
        exp: Math.floor(Date.now() / 1000) + 60 * 60
      },
      secret
    )
  };
});

router.get(
  "/getUser-token",
  jwtAuth({
    secret
  }),
  async ctx => {
    // 驗證通過,state.user
    console.log(ctx.state.user);
    
    //獲取session
    ctx.body = {
      message: "獲取數(shù)據(jù)成功",
      userinfo: ctx.state.user.data 
    };
  }
)

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000)
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  </head>

  <body>
    <div id="app">
      <div>
        <input v-model="username" />
        <input v-model="password" />
      </div>
      <div>
        <button v-on:click="login">Login</button>
        <button v-on:click="logout">Logout</button>
        <button v-on:click="getUser">GetUser</button>
      </div>
      <div>
        <button @click="logs=[]">Clear Log</button>
      </div>
      <!-- 日志 -->
      <ul>
        <li v-for="(log,idx) in logs" :key="idx">
          {{ log }}
        </li>
      </ul>
    </div>
    <script>
      axios.interceptors.request.use(
        config => {
          const token = window.localStorage.getItem("token");
          if (token) {
            // 判斷是否存在token宁玫,如果存在的話,則每個http header都加上token
            // Bearer是JWT的認(rèn)證頭部信息
            config.headers.common["Authorization"] = "Bearer " + token;
          }
          return config;
        },
        err => {
          return Promise.reject(err);
        }
      );

      axios.interceptors.response.use(
        response => {
          app.logs.push(JSON.stringify(response.data));
          return response;
        },
        err => {
          app.logs.push(JSON.stringify(response.data));
          return Promise.reject(err);
        }
      );
      var app = new Vue({
        el: "#app",
        data: {
          username: "test",
          password: "test",
          logs: []
        },
        methods: {
          async login() {
            const res = await axios.post("/login-token", {
              username: this.username,
              password: this.password
            });
            localStorage.setItem("token", res.data.token);
          },
          async logout() {
            localStorage.removeItem("token");
          },
          async getUser() {
            await axios.get("/getUser-token");
          }
        }
      });
    </script>
  </body>
</html>
  • 用戶在登錄的時候欧瘪,服務(wù)端生成一個Token給客戶端,客戶端后續(xù)的請求都要帶上這個token匙赞,服務(wù)端解析token來獲取用戶信息,并響應(yīng)用戶的請求涌庭,token會有過期時間芥被,客戶端登出也會廢棄token,但服務(wù)端不會有任何操作
  • 與token簡單對比
  • session要求服務(wù)端存儲信息坐榆,并且根據(jù)id能夠檢索拴魄,而token不需要,因為信息就在token中席镀,這樣實現(xiàn)就實現(xiàn)了服務(wù)器端的無狀態(tài)化匹中,在大規(guī)模的系統(tǒng)中,對每個請求都檢索會話信息的可能是一個復(fù)雜和耗時的過程豪诲,但另外一方面服務(wù)器要通過token來解析用戶身份也需要定義好相應(yīng)的協(xié)議顶捷,比如jwt.
  • session一般通過cookie來交互,而token方式更加靈活屎篱,可以是cookie焊切,也可以是 header扮授,也可以放在請求的內(nèi)容中。不使用cookie可以帶來跨域上的便利性专肪。
  • token的生成方式更加多樣化刹勃,可以由第三方模塊來提供。
  • token若被盜用嚎尤,服務(wù)端無法感知荔仁,cookie信息存儲在用戶自己電腦中,被盜用風(fēng)險略小芽死。
JWT(JSON WEB TOKEN)原理解析
  1. Bearer Token包含三個組成部分:令牌頭乏梁、payload、哈希eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoidGVzdCIsImV4cCI6MTU2NzY5NjEzNCwiaWF0Ij oxNTY3NjkyNTM0fQ.OzDruSCbXFokv1zFpkv22Z_9A JGCHG5fT_WnEaf72EA
    第三個參數(shù) ??? base64 可逆
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoidGVzdCIsImV4cCI6MTU2NjM5OTc3MSwiaWF0Ij oxNTY2Mzk2MTcxfQ.nV6sErzfZSfWtLSgebAL9nx2wg-LwyGLDRvfjQeF04U
  2. 簽名:默認(rèn)使用base64對payload編碼关贵,使用hs256算法對令牌頭遇骑、payload和密鑰進行簽名生成 哈希
  3. 驗證:默認(rèn)使用hs256算法對hs256算法對令牌中數(shù)據(jù)簽名并將結(jié)果和令牌中哈希比對

OAuth(開放授權(quán))

概念:三方登入主要基本于OAuth 2.0 OAuth協(xié)議為用戶資源的授權(quán)提供了一個案例的,開放而又簡易的標(biāo)準(zhǔn)揖曾,與以往的授權(quán)方式不同之處是OAUTH的授權(quán)不會使第三方觸及到用戶的賬號信息落萎,如用戶名與密碼,即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權(quán)炭剪,因此OAUTH是安全的

OAUTH的登錄流程

<html>

<head>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

</head>

<body>
  <div id="app">
    <button @click='oauth()'>Login with Github</button>
    <div v-if="userInfo">
      Hello {{userInfo.name}}
      <img :src="userInfo.avatar_url" />
    </div>
  </div>
  <script>

  </script>
  <script>
    axios.interceptors.request.use(
      config => {
        const token = window.localStorage.getItem("token");
        if (token) {
          // 判斷是否存在token练链,如果存在的話,則每個http header都加上token
          // Bearer是JWT的認(rèn)證頭部信息
          config.headers.common["Authorization"] = "Bearer " + token;
        }
        return config;
      },
      err => {
        return Promise.reject(err);
      }
    );

    axios.interceptors.response.use(
      response => {
        app.logs.push(JSON.stringify(response.data));
        return response;
      },
      err => {
        app.logs.push(JSON.stringify(response.data));
        return Promise.reject(err);
      }
    );
    var app = new Vue({
      el: "#app",
      data: {
        logs: [],
        userInfo: null
      },
      methods: {
        async oauth() {
          window.open('/auth/github/login', '_blank')
          const intervalId = setInterval(() => {
            console.log("等待認(rèn)證中..");
            if (window.localStorage.getItem("authSuccess")) {
              clearInterval(intervalId);
              window.localStorage.removeItem("authSuccess");
              this.getUser()
            }
          }, 500);
        },
        async getUser() {
          const res = await axios.get("/auth/github/userinfo");
          console.log('res:',res.data)
          this.userInfo = res.data
        }
      }
    });
  </script>
</body>
</html>
const Koa = require('koa')
const router = require('koa-router')()
const static = require('koa-static')
const app = new Koa();
const axios = require('axios')
const querystring = require('querystring')
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");
const accessTokens = {}

const secret = "it's a secret";
app.use(static(__dirname + '/'));
const config = {
    client_id: '73a4f730f2e8cf7d5fcf',
    client_secret: '74bde1aec977bd93ac4eb8f7ab63352dbe03ce48',

}

router.get('/auth/github/login', async (ctx) => {
    var dataStr = (new Date()).valueOf();
    //重定向到認(rèn)證接口,并配置參數(shù)
    var path = `https://github.com/login/oauth/authorize?${querystring.stringify({ client_id: config.client_id })}`;

    //轉(zhuǎn)發(fā)到授權(quán)服務(wù)器
    ctx.redirect(path);
})

router.get('/auth/github/callback', async (ctx) => {
    console.log('callback..')
    const code = ctx.query.code;
    const params = {
        client_id: config.client_id,
        client_secret: config.client_secret,
        code: code
    }
    let res = await axios.post('https://github.com/login/oauth/access_token', params)
    const access_token = querystring.parse(res.data).access_token
    const uid = Math.random() * 99999
    accessTokens[uid] = access_token

    const token = jwt.sign(
        {
            data: uid,
            // 設(shè)置 token 過期時間奴拦,一小時后,秒為單位
            exp: Math.floor(Date.now() / 1000) + 60 * 60
        },
        secret
    )
    ctx.response.type = 'html';
    console.log('token:', token)
    ctx.response.body = ` <script>window.localStorage.setItem("authSuccess","true");window.localStorage.setItem("token","${token}");window.close();</script>`;
})

router.get('/auth/github/userinfo', jwtAuth({
    secret
}), async (ctx) => {
    // 驗證通過绿鸣,state.user
    console.log('jwt playload:', ctx.state.user)
    const access_token = accessTokens[ctx.state.user.data]
    res = await axios.get('https://api.github.com/user?access_token=' + access_token)
    console.log('userAccess:', res.data)
    ctx.body = res.data
})

app.use(router.routes()); /*啟動路由*/
app.use(router.allowedMethods());
app.listen(7001);
單點登錄

...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暂氯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子株旷,更是在濱河造成了極大的恐慌,老刑警劉巖晾剖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沽损,居然都是意外死亡循头,警方通過查閱死者的電腦和手機炎疆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門形入,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亿遂,你說我怎么就攤上這事渺杉。” “怎么了是越?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浦徊。 經(jīng)常有香客問我,道長辑畦,這世上最難降的妖魔是什么腿倚? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任蚯妇,我火速辦了婚禮,結(jié)果婚禮上箩言,老公的妹妹穿的比我還像新娘。我一直安慰自己饭豹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布拄衰。 她就那樣靜靜地躺著饵骨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪居触。 梳的紋絲不亂的頭發(fā)上老赤,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天抬旺,我揣著相機與錄音,去河邊找鬼嚷狞。 笑死,一個胖子當(dāng)著我的面吹牛床未,可吹牛的內(nèi)容都是我干的振坚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼啃洋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宏娄?” 一聲冷哼從身側(cè)響起逮壁,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窥淆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扛伍,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡词裤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逆航。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纸泡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚤假,到底是詐尸還是另有隱情,我是刑警寧澤磷仰,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布境蔼,位于F島的核電站,受9級特大地震影響箍土,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吴藻,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一沟堡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧航罗,春花似錦、人聲如沸粥血。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽七问。三九已至,卻和暖如春械巡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讥耗。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蔼卡,地道東北人挣磨。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓荤懂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親节仿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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