大公司系統(tǒng)太多,如何實(shí)現(xiàn)賬號(hào)互通?

最近開(kāi)發(fā)新產(chǎn)品,然后老板說(shuō)我們現(xiàn)在系統(tǒng)太多了,每次切換系統(tǒng)登錄太麻煩了扯罐,能不能做個(gè)優(yōu)化,同一賬號(hào)互通掉烦衣。作為一個(gè)資深架構(gòu)獅歹河,老板的要求肯定要滿足掩浙,安排!
圖片

一個(gè)公司產(chǎn)品矩陣比較豐富的時(shí)候秸歧,用戶在不同系統(tǒng)之間來(lái)回切換厨姚,固然對(duì)產(chǎn)品用戶體驗(yàn)上較差,并且增加用戶密碼管理成本键菱。

也沒(méi)有很好地利用內(nèi)部流量進(jìn)行用戶打通谬墙,并且每個(gè)產(chǎn)品的獨(dú)立體系會(huì)導(dǎo)致產(chǎn)品安全度下降。

因此實(shí)現(xiàn)集團(tuán)產(chǎn)品的單點(diǎn)登錄對(duì)用戶使用體驗(yàn)以及效率提升有很大的幫助经备。那么如何實(shí)現(xiàn)統(tǒng)一認(rèn)證呢拭抬?我們先了解一下傳統(tǒng)的身份驗(yàn)證方式。

傳統(tǒng) Session 機(jī)制及身份認(rèn)證方案

| Cookie 與服務(wù)器的交互

圖片

眾所周知侵蒙,http 是無(wú)狀態(tài)的協(xié)議造虎,因此客戶每次通過(guò)瀏覽器訪問(wèn) web。

頁(yè)面蘑志,請(qǐng)求到服務(wù)端時(shí)累奈,服務(wù)器都會(huì)新建線程贬派,打開(kāi)新的會(huì)話急但,而且服務(wù)器也不會(huì)自動(dòng)維護(hù)客戶的上下文信息。

比如我們現(xiàn)在要實(shí)現(xiàn)一個(gè)電商內(nèi)的購(gòu)物車功能搞乏,要怎么才能知道哪些購(gòu)物車請(qǐng)求對(duì)應(yīng)的是來(lái)自同一個(gè)客戶的請(qǐng)求呢波桩?
圖片

因此出現(xiàn)了 session 這個(gè)概念,session 就是一種保存上下文信息的機(jī)制请敦,他是面向用戶的镐躲,每一個(gè) SessionID 對(duì)應(yīng)著一個(gè)用戶,并且保存在服務(wù)端中侍筛。

session 主要以 cookie 或 URL 重寫為基礎(chǔ)的來(lái)實(shí)現(xiàn)的萤皂,默認(rèn)使用 cookie 來(lái)實(shí)現(xiàn),系統(tǒng)會(huì)創(chuàng)造一個(gè)名為 JSESSIONID 的變量輸出到 cookie 中匣椰。

JSESSIONID 是存儲(chǔ)于瀏覽器內(nèi)存中的裆熙,并不是寫到硬盤上的,如果我們把瀏覽器的cookie 禁止禽笑,則 web 服務(wù)器會(huì)采用 URL 重寫的方式傳遞 Sessionid入录,我們就可以在地址欄看到 sessionid=KWJHUG6JJM65HS2K6 之類的字符串。

通常 JSESSIONID 是不能跨窗口使用的佳镜,當(dāng)你新開(kāi)了一個(gè)瀏覽器窗口進(jìn)入相同頁(yè)面時(shí)僚稿,系統(tǒng)會(huì)賦予你一個(gè)新的 sessionid,這樣我們信息共享的目的就達(dá)不到了蟀伸。

| 服務(wù)器端的 session 的機(jī)制

當(dāng)服務(wù)端收到客戶端的請(qǐng)求時(shí)候蚀同,首先判斷請(qǐng)求里是否包含了 JSESSIONID 的 sessionId缅刽,如果存在說(shuō)明已經(jīng)創(chuàng)建過(guò)了,直接從內(nèi)存中拿出來(lái)使用蠢络,如果查詢不到拷恨,說(shuō)明是無(wú)效的。

如果客戶請(qǐng)求不包含 sessionid谢肾,則為此客戶創(chuàng)建一個(gè) session 并且生成一個(gè)與此 session 相關(guān)聯(lián)的 sessionid腕侄,這個(gè) sessionid 將在本次響應(yīng)中返回給客戶端保存。

對(duì)每次 http 請(qǐng)求芦疏,都經(jīng)歷以下步驟處理:

  • 服務(wù)端首先查找對(duì)應(yīng)的 cookie 的值(sessionid)冕杠。

  • 根據(jù) sessionid,從服務(wù)器端 session 存儲(chǔ)中獲取對(duì)應(yīng) id 的 session 數(shù)據(jù)酸茴,進(jìn)行返回分预。

  • 如果找不到 sessionid,服務(wù)器端就創(chuàng)建 session薪捍,生成 sessionid 對(duì)應(yīng)的 cookie笼痹,寫入到響應(yīng)頭中。

session 是由服務(wù)端生成的酪穿,并且以散列表的形式保存在內(nèi)存中凳干。

| 基于 session 的身份認(rèn)證流程

基于 seesion 的身份認(rèn)證主要流程如下:
圖片

因?yàn)?http 請(qǐng)求是無(wú)狀態(tài)請(qǐng)求,所以在 Web 領(lǐng)域被济,大部分都是通過(guò)這種方式解決救赐。但是這么做有什么問(wèn)題呢?我們接著看只磷。

集群環(huán)境下的 Session 困境及解決方案

隨著技術(shù)的發(fā)展经磅,用戶流量增大,單個(gè)服務(wù)器已經(jīng)不能滿足系統(tǒng)的需要了钮追,分布式架構(gòu)開(kāi)始流行预厌。
圖片

通常都會(huì)把系統(tǒng)部署在多臺(tái)服務(wù)器上,通過(guò)負(fù)載均衡把請(qǐng)求分發(fā)到其中的一臺(tái)服務(wù)器上元媚,這樣很可能同一個(gè)用戶的請(qǐng)求被分發(fā)到不同的服務(wù)器上轧叽。

因?yàn)?session 是保存在服務(wù)器上的,那么很有可能第一次請(qǐng)求訪問(wèn)的 A 服務(wù)器惠毁,創(chuàng)建了 session犹芹,但是第二次訪問(wèn)到了 B 服務(wù)器,這時(shí)就會(huì)出現(xiàn)取不到 session 的情況鞠绰。

我們知道腰埂,Session 一般是用來(lái)存會(huì)話全局的用戶信息(不僅僅是登陸方面的問(wèn)題),用來(lái)簡(jiǎn)化/加速后續(xù)的業(yè)務(wù)請(qǐng)求蜈膨。

傳統(tǒng)的 session 由服務(wù)器端生成并存儲(chǔ)屿笼,當(dāng)應(yīng)用進(jìn)行分布式集群部署的時(shí)候牺荠,如何保證不同服務(wù)器上 session 信息能夠共享呢?

| Session 共享方案

Session 共享一般有兩種思路:

  • session 復(fù)制

  • session 集中存儲(chǔ)

①session 復(fù)制

session 復(fù)制即將不同服務(wù)器上 session 數(shù)據(jù)進(jìn)行復(fù)制驴一,用戶登錄休雌,修改,注銷時(shí)肝断,將 session 信息同時(shí)也復(fù)制到其他機(jī)器上面去杈曲。
圖片

這種實(shí)現(xiàn)的問(wèn)題就是實(shí)現(xiàn)成本高,維護(hù)難度大胸懈,并且會(huì)存在延遲登問(wèn)題担扑。

②session 集中存儲(chǔ)

圖片

集中存儲(chǔ)就是將獲取 session 單獨(dú)放在一個(gè)服務(wù)中進(jìn)行存儲(chǔ),所有獲取 session 的統(tǒng)一來(lái)這個(gè)服務(wù)中去取趣钱。

這樣就避免了同步和維護(hù)多套 session 的問(wèn)題涌献。一般我們都是使用 redis 進(jìn)行集中式存儲(chǔ) session。

多服務(wù)下的登陸困境及 SSO 方案

| SSO 的產(chǎn)生背景

圖片

如果企業(yè)做大了之后首有,一般都有很多的業(yè)務(wù)支持系統(tǒng)為其提供相應(yīng)的管理和 IT 服務(wù)燕垃,按照傳統(tǒng)的驗(yàn)證方式訪問(wèn)多系統(tǒng),每個(gè)單獨(dú)的系統(tǒng)都會(huì)有自己的安全體系和身份認(rèn)證系統(tǒng)井联。

進(jìn)入每個(gè)系統(tǒng)都需要進(jìn)行登錄卜壕,獲取 session,再通過(guò) session 訪問(wèn)對(duì)應(yīng)系統(tǒng)資源低矮。

這樣的局面不僅給管理上帶來(lái)了很大的困難印叁,對(duì)客戶來(lái)說(shuō)也極不友好,那么如何讓客戶只需登陸一次军掂,就可以進(jìn)入多個(gè)系統(tǒng),而不需要重新登錄呢昨悼?
圖片

“單點(diǎn)登錄”就是專為解決此類問(wèn)題的蝗锥。其大致思想流程如下:通過(guò)一個(gè) ticket 進(jìn)行串接各系統(tǒng)間的用戶信息。

| SSO 的底層原理 CAS

①CAS 實(shí)現(xiàn)單點(diǎn)登錄流程

我們知道對(duì)于完全不同域名的系統(tǒng)率触,cookie 是無(wú)法跨域名共享的终议,因此 sessionId 在頁(yè)面端也無(wú)法共享,因此需要實(shí)現(xiàn)單店登錄葱蝗,就需要啟用一個(gè)專門用來(lái)登錄的域名如(ouath.com)來(lái)提供所有系統(tǒng)的 sessionId穴张。

當(dāng)業(yè)務(wù)系統(tǒng)被打開(kāi)時(shí),借助中心授權(quán)系統(tǒng)進(jìn)行登錄两曼,整體流程如下:

  • 當(dāng) b.com 打開(kāi)時(shí)皂甘,發(fā)現(xiàn)自己未登陸,于是跳轉(zhuǎn)到 ouath.com 去登陸

  • ouath.com 登陸頁(yè)面被打開(kāi)悼凑,用戶輸入帳戶/密碼登陸成功

  • ouath.com 登陸成功偿枕,種 cookie 到 ouath.com 域名下

  • 把 sessionid 放入后臺(tái) redis璧瞬,存放<ticket,sesssionid>數(shù)據(jù)結(jié)構(gòu)渐夸,然后頁(yè)面重定向到 A 系統(tǒng)

  • 當(dāng) b.com 重新被打開(kāi)嗤锉,發(fā)現(xiàn)仍然是未登陸,但是有了一個(gè) ticket 值

  • 當(dāng) b.com 用 ticket 值墓塌,到 redis 里查到 sessionid瘟忱,并做 session 同步,然后種 cookie 給自己苫幢,頁(yè)面原地重定向

  • 當(dāng) b.com 打開(kāi)自己頁(yè)面酷誓,此時(shí)有了 cookie,后臺(tái)校驗(yàn)登陸狀態(tài)态坦,成功

整個(gè)交互流程圖如下:
圖片

②單點(diǎn)登錄流程演示

CAS 登錄服務(wù) demo 核心代碼如下:

用戶實(shí)體類:

public class UserForm implements Serializable{
private static final long serialVersionUID = 1L;

private String username;
private String password;
private String backurl;

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public String getBackurl() {
    return backurl;
}

public void setBackurl(String backurl) {
    this.backurl = backurl;
}

}

登錄控制器:

@Controller
public class IndexController {
    @Autowired
    private RedisTemplate redisTemplate;

@GetMapping("/toLogin")
public String toLogin(Model model,HttpServletRequest request) {
    Object userInfo = request.getSession().getAttribute(LoginFilter.USER_INFO);
    //不為空盐数,則是已登陸狀態(tài)
    if (null != userInfo){
        String ticket = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(ticket,userInfo,2, TimeUnit.SECONDS);
        return "redirect:"+request.getParameter("url")+"?ticket="+ticket;
    }
    UserForm user = new UserForm();
    user.setUsername("laowang");
    user.setPassword("laowang");
    user.setBackurl(request.getParameter("url"));
    model.addAttribute("user", user);

    return "login";
}

@PostMapping("/login")
public void login(@ModelAttribute UserForm user,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
    System.out.println("backurl:"+user.getBackurl());
    request.getSession().setAttribute(LoginFilter.USER_INFO,user);

    //登陸成功,創(chuàng)建用戶信息票據(jù)
    String ticket = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS);
    //重定向伞梯,回原url  ---a.com
    if (null == user.getBackurl() || user.getBackurl().length()==0){
        response.sendRedirect("/index");
    } else {
        response.sendRedirect(user.getBackurl()+"?ticket="+ticket);
    }
}

@GetMapping("/index")
public ModelAndView index(HttpServletRequest request) {
    ModelAndView modelAndView = new ModelAndView();
    Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);
    UserForm userInfo = (UserForm) user;
    modelAndView.setViewName("index");
    modelAndView.addObject("user", userInfo);
    request.getSession().setAttribute("test","123");
    return modelAndView;
}
}

登錄過(guò)濾器:

public class LoginFilter implements Filter {
    public static final String USER_INFO = "user";
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest,
                     ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) servletRequest;
     HttpServletResponse response = (HttpServletResponse)servletResponse;

    Object userInfo = request.getSession().getAttribute(USER_INFO);;

    //如果未登陸玫氢,則拒絕請(qǐng)求,轉(zhuǎn)向登陸頁(yè)面
    String requestUrl = request.getServletPath();
    if (!"/toLogin".equals(requestUrl)//不是登陸頁(yè)面
            &amp;&amp; !requestUrl.startsWith("/login")//不是去登陸
            &amp;&amp; null == userInfo) {//不是登陸狀態(tài)

        request.getRequestDispatcher("/toLogin").forward(request,response);
        return ;
    }

    filterChain.doFilter(request,servletResponse);
}

@Override
public void destroy() {

}
}

配置過(guò)濾器:

@Configuration
public class LoginConfig {

//配置filter生效
@Bean
public FilterRegistrationBean sessionFilterRegistration() {

    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new LoginFilter());
    registration.addUrlPatterns("/*");
    registration.addInitParameter("paramName", "paramValue");
    registration.setName("sessionFilter");
    registration.setOrder(1);
    return registration;
}
}

登錄頁(yè)面:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>enjoy login</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div text-align="center">
    <h1>請(qǐng)登陸</h1>
    <form action="#" th:action="@{/login}" th:object="${user}" method="post">
        <p>用戶名: <input type="text" th:field="*{username}" /></p>
        <p>密  碼: <input type="text" th:field="*{password}" /></p>
        <p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
        <input type="text" th:field="*{backurl}" hidden="hidden" />
    </form>
</div>


</body>
</html>

web 系統(tǒng) demo 核心代碼如下:

過(guò)濾器:

public class SSOFilter implements Filter {
    private RedisTemplate redisTemplate;

public static final String USER_INFO = "user";

public SSOFilter(RedisTemplate redisTemplate){
    this.redisTemplate = redisTemplate;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest,
                     ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;

    Object userInfo = request.getSession().getAttribute(USER_INFO);;

    //如果未登陸谜诫,則拒絕請(qǐng)求漾峡,轉(zhuǎn)向登陸頁(yè)面
    String requestUrl = request.getServletPath();
    if (!"/toLogin".equals(requestUrl)//不是登陸頁(yè)面
            &amp;&amp; !requestUrl.startsWith("/login")//不是去登陸
            &amp;&amp; null == userInfo) {//不是登陸狀態(tài)

        String ticket = request.getParameter("ticket");
        //有票據(jù),則使用票據(jù)去嘗試拿取用戶信息
        if (null != ticket){
            userInfo = redisTemplate.opsForValue().get(ticket);
        }
        //無(wú)法得到用戶信息,則去登陸頁(yè)面
        if (null == userInfo){
            response.sendRedirect("http://127.0.0.1:8080/toLogin?url="+request.getRequestURL().toString());
            return ;
        }

        /**
         * 將用戶信息喻旷,加載進(jìn)session中
         */
        UserForm user = (UserForm) userInfo;
        request.getSession().setAttribute(SSOFilter.USER_INFO,user);
        redisTemplate.delete(ticket);
    }

    filterChain.doFilter(request,servletResponse);
}

@Override
public void destroy() {

}
}

控制器:

@Controller
public class IndexController {
    @Autowired
    private RedisTemplate redisTemplate;

@GetMapping("/index")
public ModelAndView index(HttpServletRequest request) {
    ModelAndView modelAndView = new ModelAndView();
    Object userInfo = request.getSession().getAttribute(SSOFilter.USER_INFO);
    UserForm user = (UserForm) userInfo;
    modelAndView.setViewName("index");
    modelAndView.addObject("user", user);

    request.getSession().setAttribute("test","123");
    return modelAndView;
}
}

首頁(yè):

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>enjoy index</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div th:object="${user}">
    <h1>cas-website:歡迎你"></h1>
</div>
</body>
</html>

③CAS 的單點(diǎn)登錄和 OAuth2 的區(qū)別

OAuth2:三方授權(quán)協(xié)議生逸,允許用戶在不提供賬號(hào)密碼的情況下,通過(guò)信任的應(yīng)用進(jìn)行授權(quán)且预,使其客戶端可以訪問(wèn)權(quán)限范圍內(nèi)的資源槽袄。

CAS:中央認(rèn)證服務(wù)(Central Authentication Service),一個(gè)基于 Kerberos 票據(jù)方式實(shí)現(xiàn) SSO 單點(diǎn)登錄的框架锋谐,為 Web 應(yīng)用系統(tǒng)提供一種可靠的單點(diǎn)登錄解決方法(屬于 Web SSO )遍尺。

CAS 的單點(diǎn)登錄時(shí)保障客戶端的用戶資源的安全 ;OAuth2 則是保障服務(wù)端的用戶資源的安全 涮拗。

CAS 客戶端要獲取的最終信息是乾戏,這個(gè)用戶到底有沒(méi)有權(quán)限訪問(wèn)我(CAS 客戶端)的資源;OAuth2 獲取的最終信息是三热,我(oauth2 服務(wù)提供方)的用戶的資源到底能不能讓你(oauth2 的客戶端)訪問(wèn)鼓择。

因此,需要統(tǒng)一的賬號(hào)密碼進(jìn)行身份認(rèn)證就漾,用 CAS呐能;需要授權(quán)第三方服務(wù)使用我方資源,使用 OAuth2从藤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末催跪,一起剝皮案震驚了整個(gè)濱河市锁蠕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懊蒸,老刑警劉巖荣倾,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異骑丸,居然都是意外死亡舌仍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門通危,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铸豁,“玉大人,你說(shuō)我怎么就攤上這事菊碟〗诮妫” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵逆害,是天一觀的道長(zhǎng)头镊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)魄幕,這世上最難降的妖魔是什么相艇? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮纯陨,結(jié)果婚禮上坛芽,老公的妹妹穿的比我還像新娘。我一直安慰自己翼抠,他們只是感情好咙轩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著机久,像睡著了一般臭墨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膘盖,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音尤误,去河邊找鬼侠畔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛损晤,可吹牛的內(nèi)容都是我干的软棺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尤勋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼喘落!你這毒婦竟也來(lái)了茵宪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瘦棋,失蹤者是張志新(化名)和其女友劉穎稀火,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赌朋,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凰狞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沛慢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赡若。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖团甲,靈堂內(nèi)的尸體忽然破棺而出逾冬,到底是詐尸還是另有隱情,我是刑警寧澤躺苦,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布身腻,位于F島的核電站,受9級(jí)特大地震影響圾另,放射性物質(zhì)發(fā)生泄漏霸株。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一集乔、第九天 我趴在偏房一處隱蔽的房頂上張望去件。 院中可真熱鬧,春花似錦扰路、人聲如沸尤溜。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宫莱。三九已至,卻和暖如春哩罪,著一層夾襖步出監(jiān)牢的瞬間授霸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工际插, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碘耳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓框弛,卻偏偏與公主長(zhǎng)得像辛辨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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