RBAC 基于角色的权限访问控制

在20世纪90年代期间,大量专家学者和研究单位对RBAC(Role-Based Access Control)的概念进行了深入研究,先后提出了许多类型的RBAC模型,其中以美国George Mason大学信息安全技术实验室(LIST)提出的RBAC96模型最具系统性,并得到普遍的公认。

传统的权限分配的方式是将用户与权限绑定,也就是直接将权限绑定到用户身上,例如之前盛行的ACL模型。这种做法的缺陷在于效率低下,设置权限时没有统一的标准。当用户量过多时,操作复杂麻烦。

在RBAC中需要对系统的所有资源进行权限控制,系统资源可以简单地概括为静态资源(功能操作、数据列)和动态资源(数据),也分别称为对象资源和数据资源。RBAC的目标是对系统中所有对象资源和数据资源进行权限控制。

RBAC是什么

RBAC基于角色的访问控制是用户通过角色与权限进行关联,为什么不直接给用户分配权限呢?简单来说,一个用户拥有多个角色,每个角色拥有若干权限。这样就构成了“用户-角色-权限”的授权模型。在这个模型中,用户与角色、角色与权限之间是多对多的关系。

RBAC

角色是什么呢?

角色可以理解为一定数量的权限的集合,也就是权限的载体。当用户数量越来越大的时候,就需要为用户分组,每个用户组内拥有不同的用户,除了可以给用户授权外,也可以给用户组授权。如此一来,用户拥有的所有权限就是用户个人拥有的权限与该用户所在用户组拥有的权限之和。

用户组

用户组与角色有何区别呢?

用户组是用户的集合,但不是权限的集合。角色同时具有用户集合和权限集合的概念,角色是把两个集合联系在一起的中间媒介。如果用户组的权限和成员仅能被系统安全员修改的话,用户组的机制是非常接近于角色的概念的。角色也可以在用户组的基础上实现,这有利于保持原有系统中的控制关系。在这种情况下,角色相当于一个策略部件,与用户组的授权及责任关系相联系,而用户组则是实现角色的机制。因此,两者之间是策略与实现机制之间的关系。

权限是什么呢?

权限表现在对功能模块的操作,例如对上传文件的删除和修改,对菜单的访问,甚至是对页面上某个按钮或图片可见性的控制,都是属于权限的范畴。在做数据表建模时可将功能操作和资源进行统一管理,直接与权限表进行关联,这样更具便捷性和易扩展性。

可见权限分为

  • 页面权限
    Web系统由一个一个页面组成,页面构成了模块,用户能够看到这个页面的菜单,是能进入某个页面就成为页面权限。
  • 操作权限
    用户在系统中交互动作都是操作权限,典型的例如增删改查操作。
  • 数据权限
    业务管理系统中对数据私密性有要求时,哪些人可以看到哪些数据,看不到哪些数据。
权限分类

RBAC权限模型的扩展模型的完整设计

RBAC完整设计

RBAC本质

RBAC的本质是单纯地用户和权限进行解耦,将用户与角色、角色与权限关联。

用户角色权限

RBAC认为权限的过程可以抽象为:判断谁(Who)是否可以对什么(What)进行怎样(How)的访问操作(Operator),简单来说,就是对这个逻辑表达式的值是否为真(True)的求解过程。进而将权限问题转换为Who、What、How的问题,因此Who、What、How构成了访问权限的三元组。

  • Who 权限的拥有者或主体,如User、Role、Group、Actor、Principal...
  • What 权限针对的对象或资源,如Operator、Object、Class、Resource...
  • How 具体的权限,主要是Privilege,可进行正向授权和反向授权。

RBAC相关概念

  • User 用户

用户对应着系统中的具体操作者,用户可以自己拥有权限,也可以归属于零个或多个角色,也可以属于零个或多个组,用户的权限集是自身所具有的权限、所属角色具有的权限,所属组具有的权限的合集,用户与权限、角色、组之间的关系都是多对多的关系。

  • Operator 操作

Operator 表明对What的How操作,也就是Privilege+Resource的组合。

  • Privilege 权限

权限由操作与资源组成,表示对资源的一个操作。角色与权限的关系是多对多的,这一点是权限的核心。

  • Role 角色

Role 表示一定数量权限的集合,权限分配的单位与载体,目的是隔离用户和权限的逻辑关系。角色是为许多拥有相似权限的用户进行的分类管理,角色具有上下级关系,可形成树状结构,父级角色的权限是自身以及子角色权限的总和。角色作为用于与权限的代理层,解耦了权限与用户的关系,所有的授权应该给与角色而不是直接分配给用户或组。

  • Group 用户组

Group 是权限分配的单位与载体,权限不考虑分配给特定的用户而给组,组可以包含组以实现权限的继承,也可以包含用户,组内用户继承组的权限。用户与组的关系是多对多的,组可以层次化以满足不同级别权限控制的要求。
为了更好地管理用户,对用户进行分组归类。组也具有上下级关系,可形成树状结构。

  • Session会话

Session在RBAC中是一个比较隐晦的元素,准确来说,每个Session都是一个映射,一个用户到多个Role角色的映射。当一个用户激活它所有角色的一个子集时会建立一个Session会话。每个Session会话和单个User用户可以关联到一个或多个Session会话。

RBAC的关注点在于角色、用户、权限三者之间的关系即用户授权(User Assignment, UA)和权限分配(Permission Assignment,PA),关系的左右都是多对多的关系,用户可以有多个角色,角色可以包括多个用户。简单来说,用户授权和权限分配相当于中间表,事实上整个RBAC都是基于关系的模型。

RBAC中用户实际上是在扮演角色,可以使用Actor取代User,考虑到多人可以拥有相同的权限,RBAC引入了Group组的概念,Group组同样也可以看作是Actor,而User的概念就是具体到一个人。

RBAC分析模型

RBAC作为一种分析模型,主要分为四部分RBAC0~RBAC3,RBAC96模型家族中包含了这四个概念模型。

NIST(The National Institute of Standards and Technology,美国国家标准与技术研究院)标准RBAC模型由四个部件模型组成,这四个部件模型分别是:

RBAC

RBAC0(Core RBAC)基础模型

RBAC0是RBAC的核心,RBAC1、RBAC2、RBAC3都是先后在RBAC0的基础上进行扩展。

RBAC0定义了能够构成RBAC控制系统的最小的元素集合,换句话说,RBAC0定义了完全支持RBAC概念的任何系统的最低需求。

RBAC0由四部分构成:User用户、Role角色、Session会话、Permission权限

RBAC0

权限包含“操作”和“控制对象”,权限应该赋予角色而非用户。当一个用户被指派给一个角色时,此用户就拥有了该角色所包含的权限。另外会话是一个动态的概念,用户必须通过会话才可以设置角色,此时用户与激活的角色之间的映射关系。

RBAC1(Hierarchal RBAC)角色分层模型

RBAC1引入了角色间的继承关系,也就是说,角色上有了上下级的区别。

RBAC1

角色间的继承关系可分为一般继承关系和受限继承关系

  • 一般继承关系仅仅要求角色继承关系是一个绝对偏序关系,允许角色之间的多继承。
  • 受限继承关系则进一步要求角色继承关系是一个树状结构,实现角色间的单继承。

RBAC1适用于角色之间的层次明确,包含关系清晰的场景。

RBAC2(Constraint RBAC)角色限制模型

RBAC2是基于RBAC0的模型,并增加了对角色的限制:角色互斥、基数约束、先决条件角色...

RBAC2添加了责任分离关系,RBAC2的约束规定了权限被赋予角色时,或角色被赋予用户时,以及当用户在某一时刻激活一个角色时应当遵循的强制性规则。

责任分离包括静态责任分离和动态责任分离,约束与“用户-角色-权限”关系一起决定了RBAC2中用户的访问许可。

RBAC2的约束分为

  • 角色互斥

同一用户只能分配到一组互斥角色集合中最多一个角色,支持责任分离原则。互斥角色是指各自权限相互制约的两个角色,对于这类角色一个用户在某一次活动中只能被分配其中的一个角色,不能同时获得两个角色的使用权。例如,在审计活动中一个角色不能同时被指派为会计角色和审计员角色。

  • 基数约束

一个角色被分配的用户数量受限,一个用户可拥有的角色数目受限,同样一个角色对应的访问权限数量也应当受限,以控制高级权限在系统中的分配。

  • 先决条件角色

可以分配角色给用户仅当该用户已经是另一个角色的成员,对应的可以分配访问权限给角色,仅当该角色已经拥有另一种访问权限。要想获得较高的权限,需要首先拥有第一级别的权限。

  • 运行时互斥

运行中不能同时激活多个角色

RBAC3(Combines RBAC)统一模型

RBAC3是最全面的权限管理,它是基于RBAC0基础上,将RBAC1和RBAC2整合后的统一模型。

RBAC3称为统一模型,包含了RBAC1和RBAC2,利用传递性也就将RBAC0包含在内。

RBAC3

RBAC安全原则

RBAC支持公认的安全原则:

  • 最小特权的安全原则

在RBAC模型中可通过限制分配给角色权限的多少和大小来实现,分配给与某用户对应的角色的权限只要不超过该用户完成其任务的需要就可以了。

  • 责任分离原则

在RBAC模型中可通过在完成敏感任务过程中分配两个任务上相互约束的两个角色来实现,例如在清查账目时,只需要设置财务管理员和会计两个角色参加就可以了。

  • 数据抽象原则

数据抽象是借助于抽象许可权的概念实现的,例如在账目管理活动中,可使用信用、借方等抽象许可权,而不是使用操作系统提供的读、写、可执行等具体的许可权。RBAC中并强迫实现这些原则,安全管理员可以允许配置RBAC模型使其不支持这些原则。因此,RBAC支持数据抽象的程度与实现细节有关。

RBAC优缺点

  • 基于角色与权限之间的变化要比角色与用户之间的变化相对少得多,也就减少了授权管理的复杂性,降低了管理的开销。
  • 灵活地支持企业的安全策略,并对企业的变化由很大的伸缩性。
  • RBAC没有提供操作顺序控制机制,这一缺陷使得RBAC模型很难应用于要求有严格操作次序的实体系统。

数据表设计

用户账号表

应用系统过的具体操作者,用户可以拥有权限信息,也可以归属于0~n个组。用户的权限集是自身具有的权限、所属组具有的权限、所属角色具有的权限的合集。用户与群组、角色、权限之间是多对多的关系。

CREATE TABLE `web_account` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `aid` int(11) unsigned DEFAULT '0' COMMENT '账户ID 唯一编号',
  `account` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '账号',
  `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '密码',
  `salt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '密码加盐',
  `creator` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建人',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `create_date` datetime DEFAULT NULL COMMENT '创建日期',
  `updator` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新人',
  `update_time` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
  `update_date` datetime DEFAULT NULL COMMENT '修改日期',
  `login_time` int(10) unsigned DEFAULT '0' COMMENT '最近登录时间',
  `login_date` datetime DEFAULT NULL COMMENT '最近登录日期',
  `login_ip` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '最近登录IP',
  `login_error` tinyint(4) unsigned DEFAULT '0' COMMENT '错误登录次数',
  `logout_time` int(10) unsigned DEFAULT '0' COMMENT '下线时间',
  `logout_date` datetime DEFAULT NULL COMMENT '下线日期',
  `token` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '登录令牌',
  `lock` tinyint(1) DEFAULT '0' COMMENT '锁定状态 0禁用 1启用',
  `lock_time` int(10) DEFAULT '0' COMMENT '锁定时间',
  `lock_date` datetime DEFAULT NULL COMMENT '锁定日期',
  `online` tinyint(1) unsigned DEFAULT '0' COMMENT '在线状态 0下线 1在线',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注说明',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账号';

群组表

为了更好的管理用户,对用户进行分组归类,简称为用户分组。群组是具有上下级关系,可形成树状层次结构的视图。在实际业务中,群组是可以具有自己的角色信息和权限信息的。对比QQ群会发现,一个群可以有多个用户,一个用户也可以加入多个群。每个群都具有自己的权限信息,例如查看群共享等。另外,群也具有自己的角色信息。

群组关系

用户、群组、角色、权限这四者之间的关系由于自身都会存在上下层次的,相对而言有些繁琐。

CREATE TABLE `web_group` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增编号',
  `gid` int(11) unsigned DEFAULT '0' COMMENT '群组ID 唯一',
  `pid` int(11) unsigned DEFAULT '0' COMMENT '上级编号',
  `level` int(11) unsigned DEFAULT '0' COMMENT '群组层级',
  `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '层级路径',
  `type` tinyint(1) unsigned DEFAULT '1' COMMENT '群组类型',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '群组名称',
  `notice` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '群组公告',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '群组备注',
  `owner_id` int(11) unsigned DEFAULT '0' COMMENT '拥有者ID',
  `member_count` int(11) unsigned DEFAULT '0' COMMENT '成员数量',
  `children_count` int(11) unsigned DEFAULT '0' COMMENT '子群数量',
  `status` tinyint(3) unsigned DEFAULT '1' COMMENT '群组状态 0禁用 1启动',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `create_date` datetime DEFAULT NULL COMMENT '创建日期',
  `update_time` int(10) unsigned DEFAULT '0' COMMENT '更新时间',
  `update_date` datetime DEFAULT NULL COMMENT '更新日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='群组';

群组成员表

CREATE TABLE `web_group_member` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `gid` int(11) unsigned DEFAULT '0' COMMENT '群组编号',
  `pid` int(11) unsigned DEFAULT '0' COMMENT '成员父级ID',
  `gmid` int(11) unsigned DEFAULT '0' COMMENT '成员ID',
  `role` tinyint(3) unsigned DEFAULT '0' COMMENT '成员角色',
  `status` tinyint(3) unsigned DEFAULT '1' COMMENT '成员状态 0禁用 1启用 2踢出',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `create_date` datetime DEFAULT NULL COMMENT '创建日期',
  `update_time` int(10) DEFAULT '0' COMMENT '更新时间',
  `update_date` datetime DEFAULT NULL COMMENT '更新日期',
  `entry_time` int(10) unsigned DEFAULT '0' COMMENT '进入时间',
  `entry_date` datetime DEFAULT NULL COMMENT '进入日期',
  `exit_time` int(10) unsigned DEFAULT '0' COMMENT '踢出时间',
  `exit_date` datetime DEFAULT NULL COMMENT '踢出日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='群组成员';

角色表

为了对拥有相似权限的用户或群组进行分类管理,定义了角色的概念。角色具有上下层次关系,可形成树状层次结构的视图。父级角色的权限是自身及其子角色权限的综合。

CREATE TABLE `web_role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增编号',
  `rid` int(11) unsigned DEFAULT '0' COMMENT '唯一编号',
  `pid` int(11) unsigned DEFAULT '0' COMMENT '父级编号',
  `level` int(11) unsigned DEFAULT '0' COMMENT '所属层级',
  `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '层级路径',
  `type` tinyint(1) unsigned DEFAULT '1' COMMENT '角色类型',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '角色名称',
  `status` tinyint(3) unsigned DEFAULT '1' COMMENT '角色状态',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注说明',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `create_date` datetime DEFAULT NULL COMMENT '创建日期',
  `update_time` int(10) unsigned DEFAULT '0' COMMENT '更新时间',
  `update_date` datetime DEFAULT NULL COMMENT '更新日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色';

权限节点表

CREATE TABLE `web_node` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `nid` int(11) unsigned DEFAULT '0' COMMENT '唯一编号',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '节点名称',
  `module` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '模块标识',
  `controller` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '控制器标识',
  `action` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '方法标识',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '节点地址',
  `pid` int(11) unsigned DEFAULT '0' COMMENT '父级编号',
  `level` tinyint(3) unsigned DEFAULT '0' COMMENT '节点层级',
  `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '节点路径',
  `type` tinyint(3) unsigned DEFAULT '0' COMMENT '节点类型',
  `status` tinyint(3) unsigned DEFAULT '1' COMMENT '节点状态',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注说明',
  `create_date` datetime DEFAULT NULL COMMENT '创建日期',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `update_date` datetime DEFAULT NULL COMMENT '更新日期',
  `update_time` int(10) unsigned DEFAULT '0' COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限节点';

未完待续...

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