-
Notifications
You must be signed in to change notification settings - Fork 683
/
Copy path有赞权限系统(SAM).md.html
649 lines (552 loc) · 43.6 KB
/
有赞权限系统(SAM).md.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>有赞权限系统(SAM).md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决(上).md.html">Java中9种常见的CMS GC问题分析与解决(上).md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决(下).md.html">Java中9种常见的CMS GC问题分析与解决(下).md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类:Unsafe应用解析.md.html">Java魔法类:Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化:优化 select count().md.html">MySQL优化:优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC(多版本并发控制).md.html">MySQL的MVCC(多版本并发控制).md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始,聊微服务架构.md.html">从SpringCloud开始,聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时,我们在讨论些神马?.md.html">当我们在讨论CQRS时,我们在讨论些神马?.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析(Eureka、Zookeeper、Nacos).md.html">服务注册与发现原理剖析(Eureka、Zookeeper、Nacos).md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶:Dockerfile 高阶使用指南及镜像优化.md.html">进阶:Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析(上).md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析(上).md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析(下).md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析(下).md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>有赞权限系统(SAM)</h1>
<p>有赞作为一个商家服务公司,通过产品和服务,帮助互联网时代的生意人成功。在新零售的浪潮下,有赞零售为商家提供不同规模的门店和网店经营解决方案,帮助零售商家们快速进入新零售时代。与传统网上商城场景不同,零售面对着全新的业务场景和难题,一家运转成熟的新零售店铺,通常需要包括老板、店长、客服、收银员、核销员、仓管、财务等十余个不同能力的角色分工、搭配。摆在零售商家们眼前的一大难题是,如何优雅的管理各个员工,自由分配角色,无痛又润滑地解决员工角色管理问题。在充分分析零售行业业务场景,员工角色管理方案的不断探索讨论后,权限系统SAM(Security Access Manager)应运而生,SAM是有赞零售在员工角色权限管理道路上探索的里程碑,支持着零售PC、App和Pad产品的权限业务,任何一家使用了有赞零售的零售店都可以通过SAM权限系统提供的服务来灵活的给店里员工灵活分配角色,责任到人,以此提高店铺运转效率;支撑零售业务的同时,抽象出了一套权限管理框架,对其他业务线产品(微商城)进行同样支持。</p>
<p>在介绍SAM系统之前,首先以几个案例来理解权限系统的概念和设计。</p>
<p>计算机世界中的许多事物是现实世界的一个阴影,现实中所见的许多模式/概念在计算机世界里都能找到,权限作为现实世界随处可见的概念,在我们谈论私有制、所有权时,时常会谈及权限,在计算机世界中,权限在许多系统中举足轻重。一切皆文件的Linux操作系统,为大多技术人员所熟悉,在这样一个多用户操作系统里,每个用户有自己的工作空间,通过把权限落在文件上实现对资源的管理。曾记否,qq里隐身对她可见,怕她看不见,下线又上线,却依旧被视而不见;曾记否,亲密无间的恋人们,分手后变成了最熟悉的陌生人,悲痛伤心之余,微信、电话、 qq拉黑。上述这些,都是计算机中利用权限系统的典型案例,在qq隐身案例中,你对女神隐身可见,实际上是赋予了她可以看到你的隐身状态(真实状态)的权限,当然你也赋予了人家伤害你的权限;恋人的案例中,恋人们把对方拉到了黑名单用户组,这样一来,他们就看不见相互动态,成为最熟悉的陌生人;从此,从你的全世界路过。</p>
<h2>RBAC</h2>
<p>上面例子,我们可以抽象出这样的模式:“Who对What(Which)进行How的操作” 。例如,恋人们的例子,在你拉黑对方后,在朋友圈中你(Who)将看不到(How)对方的消息(What)。这是一个经典的RBAC(基于角色的权限访问控制)权限模型。RBAC认为权限授权实际上是Who、What、How的问题。在RBAC模型中,Who、What、How构成了访问权限三元组,也就是“Who(权限的拥用者或主体)对What(Which)(权限针对的对象或资源)进行How(具体的权限)的操作”。</p>
<p>RBAC模型引入了“角色”的概念。所谓“角色”就是一个或一群用户在系统中可执行操作的集合,它是一个用户的集合,又是一个授权许可的集合。通过将角色指派给用户,为角色赋予权限的方式,使用户和权限通过角色间接相联系。RBAC基本模型如图所示:
<img src="assets/d74cd07783560399100d9edb7840746c.jpeg" alt="sam" /></p>
<p>在RBAC中,用户与角色之间、角色与权限之间都是多对多的关系。会话是一个用户对多个角色的映射,此时的用户权限可以为激活角色权限的并集。RBAC对资源授权管理过程分为两个部分,首先实现访问权限与角色相关联,然后再实现角色与用户相关联,从而实现了用户与访问权限的逻辑分离。</p>
<h2>权限系统SAM</h2>
<h3>SAM权限系统模型设计</h3>
<p>RBAC模型不同于强制存取控制以及自由选定存取控制直接赋予使用者权限,是将权限赋予角色。在RBAC中,权限与角色相关联,用户通过成为适当角色成员而得到这些角色的权限,角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。RBAC相对于传统访问控制更为中性且更具灵活性的存取控制技术。从一家零售店铺员工角色管理角度看,设置角色是为了完成各种工作而创造,员工则根据它的责任和资格来被指派相应的角色,员工应该可以很容易地从一个角色被指派到另一个角色。因此,零售选择了基于RBAC模型来实现权限系统解决商家们管理员工角色问题。</p>
<p>依据RBAC模型思想,SAM权限系统业务模型设计为员工管理和权限管理两部分,员工管理主要指管理员工以及为员工指派角色,权限管理主要指管理菜单、页面、按钮、API等资源,通过定义最基本的业务功能点作为权限点,实现管理角色对资源主体的请求,构成“用户-角色-权限-资源”的授权模型。<img src="assets/d02bd76ecd033c3d5c2838d152514fce.jpeg" alt="sam" /></p>
<p>下面是SAM权限系统模型中的一些通用语言:</p>
<ul>
<li>员工:角色的载体,权限的实行者</li>
<li>角色:角色是权限集进一步映射。业务系统可动态管理角色,各业务为方便用户使用可提供给默认角色列表,满足不同的员工权限</li>
<li>权限点:全局唯一的用来表示某一个功能点对应的权限的状态</li>
<li>功能点:逻辑上定义的用来描述系统资源的最小基本单位,每一个功能点都对应唯一一个权限点</li>
<li>功能集(权限集):即功能点的集合,有一组功能点按照特定格式进行组合</li>
<li>API:请求系统资源的通道和动作,拥有功能集属性</li>
<li>菜单:将系统资源组织后展示给请求者的入口,拥有功能集属性</li>
<li>页面:被当做一种特殊的菜单,拥有URL属性</li>
<li>按钮:页面中更细粒度的资源入口,被当作一种特殊的菜单</li>
</ul>
<h3>SAM权限系统模型的实现</h3>
<p>在传统的RBAC模型中,通常通过一张关系表来保存角色与权限集的对应关系,实现权限与角色相关联。可以预见的是,随着零售业务的不断发展会积累下不计其数的功能点,导致关联表的数据极难维护和使用。SAM权限系统利用进制转换的策略解决了这个问题 ,同时提高了存储效率以及权限判定效率。一个基本类型为Long的十进制数字,它也可以看做是由64位0或1组成的二进制。在SAM系统模型设计中,每一个功能点定义为一个权限点,该权限点由idx和pos两个属性确保是全局唯一的权限点。idx表示第几个Long型空间,pos表示Long型对应的二进制数中所处的位置,64位长度即可代表64个不同能功能点。当64位满时无法再容放更多的功能点,这时idx属性会自增,重新申请一个Long型空间。如此一个64位的Long数字,通过0或1的组合,即可表示最多对64个不同的功能点所拥有权限的状态描述。</p>
<p>例如:权限集{1}表示拥有idx=0,pos=0对应功能点的权限,权限集{-1,1}表示拥有idx=0,pos∈[0,1,2,..,63]与idx=1,pos=0对应功能点的权限。</p>
<p>SAM权限系统将资源与所代表的功能点的关联关系通过进制的方式管理起来,采用计算机进制的思想,抽象出功能集换算公式来完成资源与二进制之间的映射,以及角色与二进制的映射。</p>
<p>权限集换算公式:
{(idx0,pos0),(idx0,pos1)…(idxN,posM)} => {Long0,Long1…LongN}</p>
<p>SAM权限系统同样通过进制思想实现“Who对What进行了How的操作”,角色请求某个资源(菜单/API)时,通过权限校验计算公式——进制按位“与”运算操作的思想(见下)得出该角色是否拥有访问资源的权限。采用进制来实现运算,权限判定的效率会变得更加的高效。例如,一个仓管在点击一个商品库存菜单时,背后的权限校验计算公式,其实是将角色的权限集与资源的权限集进行按位与计算,任意一对序号为idx的Long算得不为0,即两集合有公共的功能集,认为该角色拥有对资源访问的权限。</p>
<p>权限校验计算公式:
{Long0,Long1…LongN} & {Long0,Long1…LongM}</p>
<p>SAM权限系统模型的实现遵循RBAC模型中的最小权限原则,责任分离原则和数据抽象原则三大原则,通过最小权限原则可以将角色配置成其完成任务所需要的最小的功能集;有了责任分离原则可以通过调用相互独立互斥的角色来共同完成敏感的任务而体现,比如要求一个仓管和商品管理员共同参与一个商品。数据抽象则可以通过权限的抽象来体现,如仓管操作商品发货,库存管理等抽象权限,而不用操作系统提供的典型的读、写、执行权限。</p>
<h3>SAM权限系统架构</h3>
<p>零售通过PC、App和Pad来满足不同商家的终端需求,因此SAM权限系统需要满足零售不同客户端权限业务场景,同时也要支持微商城产品权限业务。SAM权限系统采用微服务的方式对外提供服务,采用分布式分层架构实现,主要包括客户端和服务端两部分,客户端以轻量的方式嵌入在业务系统,提供给不同业务系统实现角色访问资源的控制;服务端通过提供Dubbo服务,Nova服务跟客户端进行交互。服务端主要对员工,菜单,角色,API,功能点进行数据管理。SAM作为基础服务,每天的请求量巨大,通过Redis缓存来解决性能问题,选用Druid作为数据库连接池,管理着数据库的连接以及释放。同时,通过对接天网监控平台来观察系统运行状态,提高系统的稳定性。<img src="assets/48d5d0d3a957a8983fb4739c88a56034.jpeg" alt="sam" />有赞零售系统基于SAM实现的角色对于资源的访问控制主要是API校验和菜单渲染,任何一家零售店登入有赞零售系统后,点击页面中的某一个菜单或者页面元素(按钮,链接...),都会进行菜单渲染以及API接口的校验。由于两部分调用量巨大,同时不同的客户端请求量不同,防止相互之间干扰,因此将菜单渲染,API校验等能力在不同的客户端中各自实现。</p>
<h4>菜单渲染</h4>
<p>SAM通过客户端的方式进行接入,菜单渲染在客户端一侧进行。目前SAM已经提供了php/node js两套客户端,供web层进行接入和渲染。
菜单渲染的过程可以分为三点:
一、结点定位
按照系统功能的划分,菜单通常以一棵树的形式进行展现。以零售PC后台为例,所有在页面中展示的元素,都认为是一种菜单,这样的菜单元素包括:菜单、页面、按钮。在后台访问时,用户停留的菜单通常是页面,页面有一个全局唯一的属性:URL,往上:可以通过父菜单找到根结点,往下,页面下可能包含一些子菜单——按钮。因此SAM只需要根据当前请求的URL,即可在后台菜单树中定位到唯一的页面菜单,同时获得该菜单的结点路径以及拥有的按钮。
二、权限计算
我们已经获得了用户的角色权限和完整的菜单树,根据每个菜单结点的权限集,可以计算出当前用户对结点的访问权。根据计算结果,客户端对菜单可以进行区分渲染,比如:用户通过拼URL访问一个无权限页面时会提示非法,无权限访问的菜单和按钮会自动置灰不可点击。
三、属性传递
默认菜单不具备URL属性。菜单的URL属性通过子菜单的URL传递生成,SAM会选择第一个有权限的子菜单的URL作为父结点的属性,并逐级传递到一级菜单。</p>
<p><img src="assets/480986561508b897edad6ad9018485ed.jpeg" alt="sam" /></p>
<h4>API权限校验</h4>
<p>零售系统中除了菜单外,API是另一种被请求的资源类型。API校验是除了菜单渲染外另一道权限控制的保障。通过卡门(API网关)的API请求转发到具体业务系统时,嵌入在业务系统中的SAM API校验客户端会首先通过上面的权限校验计算公式对该角色是否具有权限访问这个API进行判定,若权限校验通过则执行后面业务逻辑。具体流程如下图所示<img src="assets/99443ea1c95caee396ac2473bf35f7e4.jpeg" alt="sam" /></p>
<p>API权限校验的伪代码实现:</p>
<pre><code class="language-java">#权限不通过错误码提示信息
AUTHPERM_ERROR(231000401,"您没有权限执行该操作!")
# 织入点
@Before("@annotation(com.youzan.sam.common.Auth)")
# 切面处理方法
def handle(JoinPoint pjp):
# 可以启动时或者运行时控制该开关是否对API进行权限校验
if(!enable):
return
# 权限校验结果包装对象
def pass=checkPermission()
# 权限校验执行成功
if (pass.isSuccess()):
# 权限校验通过
if(pass.getData().get("isSuccess")):
return
# 权限校验不通过
else:
throw new BusinessException(AUTHPERM_ERROR.getCode,AUTHPERM_ERROR.getMessage());
# 权限校验执行失败
else:
throw BusinessException(pass.getCode(), pass.getMessage())
# 权限校验方法
def checkPermission():
# 判断是否需要走权限校验,对于某些内部调用可以直接跳过
{...}
# 获取卡门(API网关)隐式参数,运用了dubbo的隐式传参的能力
def kdt_id=RpcContext.getContext().getAttachment(Constants.KDT_ID_KEY)
def admin_id=RpcContext.getContext().getAttachment(Constants.ADMIN_ID_KEY)
def service = RpcContext.getContext().getAttachment(Constants.SERVICE_KEY)
def method = RpcContext.getContext().getAttachment(Constants.METHOD_KEY)
def version = RpcContext.getContext().getAttachment(Constants.VERSION_KEY)
# 上述参数的校验
{...}
# 通过StaffPermServiceProxy获取角色的权限集
def staffPerm=StaffPermServiceProxy.getStaffPerms(adminId, kdtId)
# 通过APIPermServiceProxy获取API的权限集
def apiPerm=APIPermServiceProxy.getServicePerms(service, version, method)
# 运用权限校验计算公式判定该角色是否可以访问此API
{...}
# 返回结果
return pass
</code></pre>
<p>API权限校验流程可以总结如下:</p>
<ol>
<li>业务方在对应需要权限校验的API上标注@Auth注解,Spring框架会在初始化创建业务bean的时候,扫描该bean是否有@Auth注解标注的方法,对于有@Auth注解标注的,会创建代理类,然后会将该权限切面织入到代理类中;</li>
<li>业务调用有@Auth注解标注的方法时,会执行该权限校验切面逻辑,首先检查权限校验开关,判断是否需要权限校验,该开关可以在运行时动态设置;</li>
<li>如果需要,再调用AuthService的权限校验方法,AuthService会根据店铺id与用户id从SAM服务端获取员工角色权限信息,根据卡门(API网关)隐式参数中service,method,version去SAM服务端获取对应API权限(相对于在对应API上直接标注权限点,这种方式更加的灵活,而且可以随着业务API版本的升级,进行很方便的升级,同时结合卡门(API网关)可以对API进行分流,不同的商家可以对应不同API的权限校验);</li>
<li>在获取到角色权限集和API权限集后,基于上面的角色与权限校验逻辑进行权限校验。校验通过,则正式发起API请求。校验不过,则提示无权限。</li>
</ol>
<h2>SAM权限系统抽象模型</h2>
<p>产品在分析完需求后,将需求交由开发去完成。SAM权限系统支撑着零售业务的同时,也支撑着微商城业务。 零售各个模块就有不同的产品支撑着,为了更好的满足服务商家的需求,以及方便产品们的分析。SAM权限系统可以抽象成如下模型,商家和产品可以从各自不同的视角,去对接SAM权限系统。例如,下图所示商家想要一个运营的角色需要有新建商品,以及查看订单的能力,同时需要一个收银员只有查看订单的能力。产品从自己的设计角度分析,对应的就是商品管理,订单管理的模块,对应的模块下有对应的商品,订单菜单,最后将角色的权限体现在页面元素和API上,例如新建商品的按钮,以及查看订单的按钮会呈现不同的渲染样式;按钮触发对应的是与后端交互的不同API,不同的角色具有API的不同执行能力。<img src="assets/13440d8f5982c2e2a7ff82706930a0d4.jpeg" alt="sam" /><img src="assets/a51455040cb05feaacc608da1dbbd8f4.jpeg" alt="sam" /></p>
<h2>未来展望</h2>
<h3>自定义角色</h3>
<p>在了解商家需求后,零售提供了8大默认角色来支持单店版的员工角色问题。零售业务错综复杂,默认角色很多时候并不能对付所有场景,现在一些自定义角色已经在某些零售店使用。未来自定义角色将全线支持各个商家,定制任意权限的任意角色。</p>
<h3>多角色</h3>
<p>有些零售商家为了缩减人力成本,一个员工常常担任多个角色,因此需要提供一个员工多角色的能力。零售业务已经在使用多角色的能力。</p>
<h3>零售中台的支持</h3>
<p>零售中台是有赞零售的一个旗舰型产品,旨在为商家提供一个覆盖线上多渠道线下多门店的全渠道解决方案,并利用数据化运营思路帮助商家拉新获客、提高复购。其业务形态非常复杂,涉及到多种角色和权限的组合,而且每个商家可能存在一些个性化需求,如何提供灵活的适配能力是SAM系统的一个挑战。</p>
<h3>自定义菜单</h3>
<p>过去后台功能的发布上线,往往是由发布系统控制,发布则功能即刻上线,一旦发现故障立时回滚。SAM通过菜单管理,可以实时控制线上的任意菜单、页面和按钮的渲染状态,从容地上下线页面和功能。</p>
<h3>技术改造</h3>
<p>SAM作为零售以及其他业务的公共基础组件,需要打造成一个高可用,高性能,易扩展,可伸缩且安全的系统。随着业务的不断接入,通过对系统的不断改造来支撑业务的不断发展。</p>
<h2>结语</h2>
<p>权限系统目前归属有赞零售技术团队,对外开放权限接入和员工服务化能力。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70998060fd2a8b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>