Shiro【授權(quán)過(guò)濾器、與ehcache整合、驗(yàn)證碼、記住我】

前言

本文主要講解的知識(shí)點(diǎn)有以下:

  • Shiro授權(quán)過(guò)濾器使用
  • Shiro緩存
    • 與Ehcache整合
  • Shiro應(yīng)用->實(shí)現(xiàn)驗(yàn)證碼功能
  • 記住我功能

一、授權(quán)過(guò)濾器測(cè)試

我們的授權(quán)過(guò)濾器使用的是permissionsAuthorizationFilter來(lái)進(jìn)行攔截。我們可以在application-shiro中配置filter規(guī)則


        <!--商品查詢需要商品查詢權(quán)限 -->
        /items/queryItems.action = perms[item:query]
        /items/editItems.action = perms[item:edit] 

測(cè)試流程:
1、在applicationContext-shiro.xml中配置filter規(guī)則

  • ``
  • /items/queryItems.action = perms[item:query]

2、用戶在認(rèn)證通過(guò)后,請(qǐng)求/items/queryItems.action
3、被PermissionsAuthorizationFilter攔截,發(fā)現(xiàn)需要“item:query”權(quán)限
4、PermissionsAuthorizationFilter 調(diào)用realm中的doGetAuthorizationInfo獲取數(shù)據(jù)庫(kù)中正確的權(quán)限
5、PermissionsAuthorizationFilter對(duì)item:query 和從realm中獲取權(quán)限進(jìn)行對(duì)比,如果“item:query”在realm返回的權(quán)限列表中,授權(quán)通過(guò)。

realm中獲取認(rèn)證的信息,查詢出該用戶對(duì)應(yīng)的權(quán)限,封裝到simpleAuthorizationInfo中,PermissionsAuthorizationFilter會(huì)根據(jù)對(duì)應(yīng)的權(quán)限來(lái)比對(duì)。


@Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {

        //從 principals獲取主身份信息
        //將getPrimaryPrincipal方法返回值轉(zhuǎn)為真實(shí)身份類(lèi)型(在上邊的doGetAuthenticationInfo認(rèn)證通過(guò)填充到SimpleAuthenticationInfo中身份類(lèi)型),
        ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();

        //根據(jù)身份信息獲取權(quán)限信息
        //從數(shù)據(jù)庫(kù)獲取到權(quán)限數(shù)據(jù)
        List<SysPermission> permissionList = null;
        try {
            permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //單獨(dú)定一個(gè)集合對(duì)象 
        List<String> permissions = new ArrayList<String>();
        if(permissionList!=null){
            for(SysPermission sysPermission:permissionList){
                //將數(shù)據(jù)庫(kù)中的權(quán)限標(biāo)簽 符放入集合
                permissions.add(sysPermission.getPercode());
            }
        }

    /*  List<String> permissions = new ArrayList<String>();
        permissions.add("user:create");//用戶的創(chuàng)建
        permissions.add("item:query");//商品查詢權(quán)限
        permissions.add("item:add");//商品添加權(quán)限
        permissions.add("item:edit");//商品修改權(quán)限
*/      //....

        //查到權(quán)限數(shù)據(jù),返回授權(quán)信息(要包括 上邊的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //將上邊查詢到授權(quán)信息填充到simpleAuthorizationInfo對(duì)象中
        simpleAuthorizationInfo.addStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }

在bean中我們已經(jīng)配置了:如果沒(méi)有權(quán)限,那么跳轉(zhuǎn)到哪個(gè)JSP頁(yè)面了


        <!-- 通過(guò)unauthorizedUrl指定沒(méi)有權(quán)限操作時(shí)跳轉(zhuǎn)頁(yè)面-->
        <property name="unauthorizedUrl" value="/refuse.jsp" />

到目前為止,現(xiàn)在問(wèn)題又來(lái)了:

1、在applicationContext-shiro.xml中配置過(guò)慮器鏈接,需要將全部的url和權(quán)限對(duì)應(yīng)起來(lái)進(jìn)行配置,比較發(fā)麻不方便使用。

2、每次授權(quán)都需要調(diào)用realm查詢數(shù)據(jù)庫(kù),對(duì)于系統(tǒng)性能有很大影響,可以通過(guò)shiro緩存來(lái)解決。


二、使用注解式和標(biāo)簽式配置授權(quán)

上面的那種方法,還是需要我們將全部的url和權(quán)限對(duì)應(yīng)起來(lái)進(jìn)行配置,是比較不方便的。我們可以使用授權(quán)的另外兩種方式

  • 注解式
  • 標(biāo)簽式

2.1注解式

如果要使用注解式,那么就必須在Spring中開(kāi)啟controller類(lèi)aop支持


    <!-- 開(kāi)啟aop,對(duì)類(lèi)代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    <!-- 開(kāi)啟shiro注解支持 -->
    <bean
        class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

在Controller中使用注解來(lái)進(jìn)行配置就行了,就不用在我們的application-shiro中全部集中配置了


    //商品信息方法
    @RequestMapping("/queryItems")
    @RequiresPermissions("item:query")//執(zhí)行queryItems需要"item:query"權(quán)限
    public ModelAndView queryItems(HttpServletRequest request) throws Exception {

        System.out.println(request.getParameter("id"));

        //調(diào)用service查詢商品列表
        List<ItemsCustom> itemsList = itemsService.findItemsList(null);

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("itemsList", itemsList);
        // 指定邏輯視圖名
        modelAndView.setViewName("itemsList");

        return modelAndView;
    }

2.2jsp標(biāo)簽 授權(quán)

這里寫(xiě)圖片描述
這里寫(xiě)圖片描述

當(dāng)調(diào)用controller的一個(gè)方法,由于該 方法加了@RequiresPermissions("item:query") ,shiro調(diào)用realm獲取數(shù)據(jù)庫(kù)中的權(quán)限信息,看"item:query"是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問(wèn),如果存在就授權(quán)通過(guò)。

當(dāng)展示一個(gè)jsp頁(yè)面時(shí),頁(yè)面中如果遇到


三、Shiro緩存

針對(duì)上邊授權(quán)頻繁查詢數(shù)據(jù)庫(kù),需要使用shiro緩存

3.1緩存流程

shiro中提供了對(duì)認(rèn)證信息和授權(quán)信息的緩存。shiro默認(rèn)是關(guān)閉認(rèn)證信息緩存的,對(duì)于授權(quán)信息的緩存shiro默認(rèn)開(kāi)啟的。主要研究授權(quán)信息緩存,因?yàn)?strong>授權(quán)的數(shù)據(jù)量大。

用戶認(rèn)證通過(guò)。

該用戶第一次授權(quán):調(diào)用realm查詢數(shù)據(jù)庫(kù)
該用戶第二次授權(quán):不調(diào)用realm查詢數(shù)據(jù)庫(kù),直接從緩存中取出授權(quán)信息(權(quán)限標(biāo)識(shí)符)。

3.2使用ehcache和Shiro整合

導(dǎo)入jar包

這里寫(xiě)圖片描述

配置緩存管理器,注入到安全管理器中


<!-- 緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>

    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

ehcache的配置文件shiro-ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--diskStore:緩存數(shù)據(jù)持久化的目錄 地址  -->
    <diskStore path="F:\develop\ehcache" />
    <defaultCache 
        maxElementsInMemory="1000" 
        maxElementsOnDisk="10000000"
        eternal="false" 
        overflowToDisk="false" 
        diskPersistent="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120" 
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

3.3緩存清空

如果用戶正常退出,緩存自動(dòng)清空。
如果用戶非正常退出,緩存自動(dòng)清空。

還有一種情況:

  • 當(dāng)管理員修改了用戶的權(quán)限,但是該用戶還沒(méi)有退出,在默認(rèn)情況下,修改的權(quán)限無(wú)法立即生效。需要手動(dòng)進(jìn)行編程實(shí)現(xiàn):在權(quán)限修改后調(diào)用realm的clearCache方法清除緩存。

清除緩存:


    //清除緩存
    public void clearCached() {
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }

3.4sessionManager

和shiro整合后,使用shiro的session管理,shiro提供sessionDao操作 會(huì)話數(shù)據(jù)。

配置sessionManager


<!-- 會(huì)話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- session的失效時(shí)長(zhǎng),單位毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 刪除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
    </bean>

注入到安全管理器中


<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 注入session管理器 -->
        <property name="sessionManager" ref="sessionManager" />

</bean>

四、驗(yàn)證碼

在登陸的時(shí)候,我們一般都設(shè)置有驗(yàn)證碼,但是我們?nèi)绻褂肧hiro的話,那么Shiro默認(rèn)的是使用FormAuthenticationFilter進(jìn)行表單認(rèn)證。

而我們的驗(yàn)證校驗(yàn)的功能應(yīng)該加在FormAuthenticationFilter中,在認(rèn)證之前進(jìn)行驗(yàn)證碼校驗(yàn)

FormAuthenticationFilter是Shiro默認(rèn)的功能,我們想要在FormAuthenticationFilter之前進(jìn)行驗(yàn)證碼校驗(yàn),就需要繼承FormAuthenticationFilter類(lèi),改寫(xiě)它的認(rèn)證方法

4.1自定義Form認(rèn)證類(lèi)


public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {

    //原FormAuthenticationFilter的認(rèn)證方法
    @Override
    protected boolean onAccessDenied(ServletRequest request,
            ServletResponse response) throws Exception {
        //在這里進(jìn)行驗(yàn)證碼的校驗(yàn)

        //從session獲取正確驗(yàn)證碼
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpSession session =httpServletRequest.getSession();
        //取出session的驗(yàn)證碼(正確的驗(yàn)證碼)
        String validateCode = (String) session.getAttribute("validateCode");

        //取出頁(yè)面的驗(yàn)證碼
        //輸入的驗(yàn)證和session中的驗(yàn)證進(jìn)行對(duì)比 
        String randomcode = httpServletRequest.getParameter("randomcode");
        if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
            //如果校驗(yàn)失敗,將驗(yàn)證碼錯(cuò)誤失敗信息,通過(guò)shiroLoginFailure設(shè)置到request中
            httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
            //拒絕訪問(wèn),不再校驗(yàn)賬號(hào)和密碼 
            return true; 
        }
        return super.onAccessDenied(request, response);
    }

}

4.2配置自定義類(lèi)

我們編寫(xiě)完自定義類(lèi)以后,是需要在Shiro配置文件中配置我們這個(gè)自定義類(lèi)的。

由于這是我們自定義的,因此我們并不需要用戶名就使用username,密碼就使用password,這個(gè)也是我們可以自定義的


<!-- 自定義form認(rèn)證過(guò)慮器 -->
<!-- 基于Form表單的身份驗(yàn)證過(guò)濾器,不配置將也會(huì)注冊(cè)此過(guò)慮器,表單中的用戶賬號(hào)、密碼及l(fā)oginurl將采用默認(rèn)值,建議配置 -->
    <bean id="formAuthenticationFilter" 
    class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
        <!-- 表單中賬號(hào)的input名稱 -->
        <property name="usernameParam" value="username" />
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password" />
 </bean>

在Shiro的bean中注入自定義的過(guò)濾器


        <!-- 自定義filter配置 -->
        <property name="filters">
            <map>
                <!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中-->
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>

在我們的Controller添加驗(yàn)證碼錯(cuò)誤的異常判斷,從我們的Controller就可以發(fā)現(xiàn),為什么我們要把錯(cuò)誤信息存放在request域?qū)ο髎hiroLoginFailure,因?yàn)槲覀兊迷贑ontroller中獲取獲取信息,從而給用戶對(duì)應(yīng)的提示


    @RequestMapping("login")
    public String login(HttpServletRequest request)throws Exception{

        //如果登陸失敗從request中獲取認(rèn)證異常信息,shiroLoginFailure就是shiro異常類(lèi)的全限定名
        String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
        //根據(jù)shiro返回的異常類(lèi)路徑判斷,拋出指定異常信息
        if(exceptionClassName!=null){
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                //最終會(huì)拋給異常處理器
                throw new CustomException("賬號(hào)不存在");
            } else if (IncorrectCredentialsException.class.getName().equals(
                    exceptionClassName)) {
                throw new CustomException("用戶名/密碼錯(cuò)誤");
            } else if("randomCodeError".equals(exceptionClassName)){
                throw new CustomException("驗(yàn)證碼錯(cuò)誤 ");
            }else {
                throw new Exception();//最終在異常處理器生成未知錯(cuò)誤
            }
        }
        //此方法不處理登陸成功(認(rèn)證成功),shiro認(rèn)證成功會(huì)自動(dòng)跳轉(zhuǎn)到上一個(gè)請(qǐng)求路徑
        //登陸失敗還到login頁(yè)面
        return "login";
    }
這里寫(xiě)圖片描述

    <TR>
        <TD>驗(yàn)證碼:</TD>
        <TD><input id="randomcode" name="randomcode" size="8" /> <img
                id="randomcode_img" src="${baseurl}validatecode.jsp" alt=""
                width="56" height="20" align='absMiddle' /> <a
                href=javascript:randomcode_refresh()>刷新</a></TD>
    </TR>

五、記住我

Shiro還提供了記住用戶名和密碼的功能

用戶登陸選擇“自動(dòng)登陸”本次登陸成功會(huì)向cookie寫(xiě)身份信息,下次登陸從cookie中取出身份信息實(shí)現(xiàn)自動(dòng)登陸。

想要實(shí)現(xiàn)這個(gè)功能,我們的認(rèn)證信息需要實(shí)現(xiàn)Serializable接口


public class ActiveUser implements java.io.Serializable {
    private String userid;//用戶id(主鍵)
    private String usercode;// 用戶賬號(hào)
    private String username;// 用戶名稱

    private List<SysPermission> menus;// 菜單
    private List<SysPermission> permissions;// 權(quán)限

}

5.1配置rememeber管理器


<!-- rememberMeManager管理器,寫(xiě)cookie,取出cookie生成用戶信息 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie" />
    </bean>
    <!-- 記住我cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- rememberMe是cookie的名字 -->
        <constructor-arg value="rememberMe" />
        <!-- 記住我cookie生效時(shí)間30天 -->
        <property name="maxAge" value="2592000" />
    </bean>

注入到安全管理器類(lèi)上


    <!-- securityManager安全管理器 -->
    <bean id="securityManager"~~~····
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 注入session管理器 -->
        <property name="sessionManager" ref="sessionManager" />
        <!-- 記住我 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>    
    </bean>

配置頁(yè)面的input名稱:


            <tr>
                <TD></TD>
                <td><input type="checkbox" name="rememberMe" />自動(dòng)登陸</td>
            </tr>

如果設(shè)置了“記住我”,那么訪問(wèn)某些URL的時(shí)候,我們就不需要登陸了。將記住我即可訪問(wèn)的地址配置讓UserFilter攔截。

        <!-- 配置記住我或認(rèn)證通過(guò)可以訪問(wèn)的地址 -->
        /index.jsp  = user
        /first.action = user
        /welcome.jsp = user

六、總結(jié)

  • Shiro的授權(quán)過(guò)程和認(rèn)證過(guò)程是類(lèi)似的,在配置文件上配置需要授權(quán)的路徑,當(dāng)訪問(wèn)路徑的時(shí)候,Shiro過(guò)濾器去找到reaml,reaml返回?cái)?shù)據(jù)以后進(jìn)行比對(duì)。
  • Shiro支持注解式授權(quán),直接在Controller方法上使用注解聲明訪問(wèn)該方法需要授權(quán)
  • Shiro還支持標(biāo)簽授權(quán),但一般很少用
  • 由于每次都要對(duì)reaml查詢數(shù)據(jù)庫(kù),性能會(huì)低。Shiro默認(rèn)是支持授權(quán)緩存的。為了達(dá)到很好的效果,我們使用Ehcache來(lái)對(duì)Shiro的緩存進(jìn)行管理
  • 配置會(huì)話管理器,對(duì)會(huì)話時(shí)間進(jìn)行控制
  • 手動(dòng)清空緩存
  • 由于驗(yàn)證用戶名和密碼之前,一般需要驗(yàn)證驗(yàn)證碼的。所以,我們要改寫(xiě)表單驗(yàn)證的功能,先讓它去看看驗(yàn)證碼是否有錯(cuò),如果驗(yàn)證碼有錯(cuò)的話,那么用戶名和密碼就不用驗(yàn)證了。
  • 將自定義的表單驗(yàn)證類(lèi)配置起來(lái)。
  • 使用Shiro提供的記住我功能,如果用戶已經(jīng)認(rèn)證了,那就不用再次登陸了。可以直接訪問(wèn)某些頁(yè)面。

如果文章有錯(cuò)的地方歡迎指正,大家互相交流。習(xí)慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號(hào):Java3y

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,409評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,569評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,360評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,895評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,123評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,559評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,742評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,981評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,363評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,622評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,354評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,707評(píng)論 2 370

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