-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxuecheng.html
More file actions
4180 lines (3489 loc) · 198 KB
/
Copy pathxuecheng.html
File metadata and controls
4180 lines (3489 loc) · 198 KB
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
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="zh-CN">
<head><meta name="generator" content="Hexo 3.9.0">
<meta charset="utf-8">
<meta name="keywords" content="ccc, 记录">
<meta name="baidu-site-verification" content="fmlEuI34ir">
<meta name="google-site-verification" content="yCy2azpds5XSuGZvis6OuA-XIGF5GuGpYRAaGfD6o48">
<meta name="360-site-verification" content="b7c11a830ef90fd1464ad6206bb7b6e7">
<meta name="description" content="学成在线项目介绍本项目包括了用户端、机构端、运营端。 核心模块包括:内容管理、媒资管理、课程搜索、订单支付、选课管理、认证授权等。
本项目采用前后端分离架构,后端采用SpringBoot、SpringCloud技术栈开发,数据库使用了My">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="renderer" content="webkit|ie-stand|ie-comp">
<meta name="mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>ccc | Ji`s Blog</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" type="text/css" href="/libs/awesome/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
<link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
<link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
<link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
<link rel="stylesheet" type="text/css" href="/css/matery.css">
<link rel="stylesheet" type="text/css" href="/css/my.css">
<style type="text/css">
</style>
<script src="/libs/jquery/jquery-2.2.0.min.js"></script>
<script src="https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset="utf-8"></script>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?4d1d73af45a62734730491a6b6c41da4";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<script>(function (i, s, o, g, r, a, m) {
i['DaoVoiceObject'] = r;
i[r] = i[r] ||
function () {
(i[r].q = i[r].q || []).push(arguments);
};
i[r].l = 1 * new Date();
a = s.createElement(o);
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
a.charset = 'utf-8';
m.parentNode.insertBefore(a, m);
})(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/xxx.js", 'daovoice');
daovoice('init', {
app_id: "xxx",
});
daovoice('update');
</script>
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https') {
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else {
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
<script>
(function(){
var src = "https://jspassport.ssl.qhimg.com/11.0.1.js?d182b3f28525f2db83acfaaf6e696dba";
document.write('<script src="' + src + '" id="sozz"><\/script>');
})();
</script>
<meta name="baidu-site-verification" content>
<style type="text/css" lang="css">
#loading-container{
position: fixed;
top: 0;
left: 0;
min-height: 100vh;
width: 100vw;
z-index: 9999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #FFF;
text-align: center;
/* loaderҳ����ʧ���ý����ķ�ʽ*/
-webkit-transition: opacity 1s ease;
-moz-transition: opacity 1s ease;
-o-transition: opacity 1s ease;
transition: opacity 1s ease;
}
.loading-image{
width: 120px;
height: 50px;
transform: translate(-50%);
}
.loading-image div:nth-child(2) {
-webkit-animation: pacman-balls 1s linear 0s infinite;
animation: pacman-balls 1s linear 0s infinite
}
.loading-image div:nth-child(3) {
-webkit-animation: pacman-balls 1s linear .33s infinite;
animation: pacman-balls 1s linear .33s infinite
}
.loading-image div:nth-child(4) {
-webkit-animation: pacman-balls 1s linear .66s infinite;
animation: pacman-balls 1s linear .66s infinite
}
.loading-image div:nth-child(5) {
-webkit-animation: pacman-balls 1s linear .99s infinite;
animation: pacman-balls 1s linear .99s infinite
}
.loading-image div:first-of-type {
width: 0;
height: 0;
border: 25px solid #49b1f5;
border-right-color: transparent;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_up .5s 0s infinite;
animation: rotate_pacman_half_up .5s 0s infinite;
}
.loading-image div:nth-child(2) {
width: 0;
height: 0;
border: 25px solid #49b1f5;
border-right-color: transparent;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_down .5s 0s infinite;
animation: rotate_pacman_half_down .5s 0s infinite;
margin-top: -50px;
}
@-webkit-keyframes rotate_pacman_half_up {0% {transform: rotate(270deg)}50% {transform: rotate(1turn)}to {transform: rotate(270deg)}}
@keyframes rotate_pacman_half_up {0% {transform: rotate(270deg)}50% {transform: rotate(1turn)}to {transform: rotate(270deg)}}
@-webkit-keyframes rotate_pacman_half_down {0% {transform: rotate(90deg)}50% {transform: rotate(0deg)}to {transform: rotate(90deg)}}
@keyframes rotate_pacman_half_down {0% {transform: rotate(90deg)}50% {transform: rotate(0deg)}to {transform: rotate(90deg)}}
@-webkit-keyframes pacman-balls {75% {opacity: .7}to {transform: translate(-100px, -6.25px)}}
@keyframes pacman-balls {75% {opacity: .7}to {transform: translate(-100px, -6.25px)}}
.loading-image div:nth-child(3),
.loading-image div:nth-child(4),
.loading-image div:nth-child(5),
.loading-image div:nth-child(6){
background-color: #49b1f5;
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
width: 10px;
height: 10px;
position: absolute;
transform: translateY(-6.25px);
top: 25px;
left: 100px;
}
.loading-text{
margin-bottom: 20vh;
text-align: center;
color: #2c3e50;
font-size: 2rem;
box-sizing: border-box;
padding: 0 10px;
text-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
@media only screen and (max-width: 500px) {
.loading-text{
font-size: 1.5rem;
}
}
.fadeout {
opacity: 0;
filter: alpha(opacity=0);
}
/* logo���ֶ��� */
@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}
@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);}}
</style>
<script>
(function () {
const loaded = function(){
setTimeout(function(){
const loader = document.getElementById("loading-container");
loader.className="fadeout" ;//ʹ�ý����ķ�������loading page
// document.getElementById("body-wrap").style.display="flex";
setTimeout(function(){
loader.style.display="none";
},1000);
},1000);//ǿ����ʾloading page 1s
};
loaded();
})()
</script><link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css">
<link rel="stylesheet" href="/css/prism-line-numbers.css" type="text/css"><style type="text/css" lang="css">
#loading-container{
position: fixed;
top: 0;
left: 0;
min-height: 100vh;
width: 100vw;
z-index: 9999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #FFF;
text-align: center;
/* loaderҳ����ʧ���ý����ķ�ʽ*/
-webkit-transition: opacity 1s ease;
-moz-transition: opacity 1s ease;
-o-transition: opacity 1s ease;
transition: opacity 1s ease;
}
.loading-image{
width: 120px;
height: 50px;
transform: translate(-50%);
}
.loading-image div:nth-child(2) {
-webkit-animation: pacman-balls 1s linear 0s infinite;
animation: pacman-balls 1s linear 0s infinite
}
.loading-image div:nth-child(3) {
-webkit-animation: pacman-balls 1s linear .33s infinite;
animation: pacman-balls 1s linear .33s infinite
}
.loading-image div:nth-child(4) {
-webkit-animation: pacman-balls 1s linear .66s infinite;
animation: pacman-balls 1s linear .66s infinite
}
.loading-image div:nth-child(5) {
-webkit-animation: pacman-balls 1s linear .99s infinite;
animation: pacman-balls 1s linear .99s infinite
}
.loading-image div:first-of-type {
width: 0;
height: 0;
border: 25px solid #49b1f5;
border-right-color: transparent;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_up .5s 0s infinite;
animation: rotate_pacman_half_up .5s 0s infinite;
}
.loading-image div:nth-child(2) {
width: 0;
height: 0;
border: 25px solid #49b1f5;
border-right-color: transparent;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_down .5s 0s infinite;
animation: rotate_pacman_half_down .5s 0s infinite;
margin-top: -50px;
}
@-webkit-keyframes rotate_pacman_half_up {0% {transform: rotate(270deg)}50% {transform: rotate(1turn)}to {transform: rotate(270deg)}}
@keyframes rotate_pacman_half_up {0% {transform: rotate(270deg)}50% {transform: rotate(1turn)}to {transform: rotate(270deg)}}
@-webkit-keyframes rotate_pacman_half_down {0% {transform: rotate(90deg)}50% {transform: rotate(0deg)}to {transform: rotate(90deg)}}
@keyframes rotate_pacman_half_down {0% {transform: rotate(90deg)}50% {transform: rotate(0deg)}to {transform: rotate(90deg)}}
@-webkit-keyframes pacman-balls {75% {opacity: .7}to {transform: translate(-100px, -6.25px)}}
@keyframes pacman-balls {75% {opacity: .7}to {transform: translate(-100px, -6.25px)}}
.loading-image div:nth-child(3),
.loading-image div:nth-child(4),
.loading-image div:nth-child(5),
.loading-image div:nth-child(6){
background-color: #49b1f5;
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
width: 10px;
height: 10px;
position: absolute;
transform: translateY(-6.25px);
top: 25px;
left: 100px;
}
.loading-text{
margin-bottom: 20vh;
text-align: center;
color: #2c3e50;
font-size: 2rem;
box-sizing: border-box;
padding: 0 10px;
text-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
@media only screen and (max-width: 500px) {
.loading-text{
font-size: 1.5rem;
}
}
.fadeout {
opacity: 0;
filter: alpha(opacity=0);
}
/* logo���ֶ��� */
@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{opacity:1;-webkit-transform:none;transform:none}}
@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);}}
</style>
<script>
(function () {
const loaded = function(){
setTimeout(function(){
const loader = document.getElementById("loading-container");
loader.className="fadeout" ;//ʹ�ý����ķ�������loading page
// document.getElementById("body-wrap").style.display="flex";
setTimeout(function(){
loader.style.display="none";
},1000);
},1000);//ǿ����ʾloading page 1s
};
loaded();
})()
</script></head>
<div id="loading-container">
<p class="loading-text"></p>
<div class="loading-image">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div><body>
<header class="navbar-fixed">
<nav id="headNav" class="bg-color nav-transparent">
<div id="navContainer" class="nav-wrapper container">
<div class="brand-logo">
<a href="/" class="waves-effect waves-light">
<img src="/medias/logo.png" class="logo-img" alt="LOGO">
<span class="logo-span">Ji`s Blog</span>
</a>
</div>
<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fa fa-navicon"></i></a>
<ul class="right">
<li class="hide-on-med-and-down">
<a href="/" class="waves-effect waves-light">
<span>首页</span>
</a>
</li>
<li class="hide-on-med-and-down">
<a href="/tags" class="waves-effect waves-light">
<span>标签</span>
</a>
</li>
<li class="hide-on-med-and-down">
<a href="/categories" class="waves-effect waves-light">
<span>分类</span>
</a>
</li>
<li class="hide-on-med-and-down">
<a href="/archives" class="waves-effect waves-light">
<span>归档</span>
</a>
</li>
<li class="hide-on-med-and-down">
<a href="/about" class="waves-effect waves-light">
<span>关于</span>
</a>
</li>
<li class="hide-on-med-and-down">
<a href="/friends" class="waves-effect waves-light">
<span>友情链接</span>
</a>
</li>
<li class="hide-on-med-and-down">
<a href="/contact" class="waves-effect waves-light">
<span>留言板</span>
</a>
</li>
<li>
<a href="#searchModal" class="modal-trigger waves-effect waves-light">
<i id="searchIcon" class="fa fa-search" title="搜索"></i>
</a>
</li>
</ul>
<div id="mobile-nav" class="side-nav sidenav">
<div class="mobile-head bg-color">
<img src="/medias/logo.png" class="logo-img circle responsive-img">
<div class="logo-name">Ji`s Blog</div>
<div class="logo-desc">
Java | Spring | Redis
</div>
</div>
<ul class="menu-list mobile-menu-list">
<li>
<a href="/" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
首页
</a>
</li>
<li>
<a href="/tags" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
标签
</a>
</li>
<li>
<a href="/categories" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
分类
</a>
</li>
<li>
<a href="/archives" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
归档
</a>
</li>
<li>
<a href="/about" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
关于
</a>
</li>
<li>
<a href="/friends" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
友情链接
</a>
</li>
<li>
<a href="/contact" class="waves-effect waves-light">
<i class="fa fa-fw fa-link"></i>
留言板
</a>
</li>
<li><div class="divider"></div></li>
<li>
<a href="https://github.com/jiyongg-code" class="waves-effect waves-light" target="_blank">
<i class="fa fa-github-square fa-fw"></i>Fork Me
</a>
</li>
</ul>
</div>
</div>
<style>
.nav-transparent .github-corner {
display: none !important;
}
.github-corner {
position: absolute;
z-index: 10;
top: 0;
right: 0;
border: 0;
transform: scale(1.1);
}
.github-corner svg {
color: #0f9d58;
fill: #fff;
height: 64px;
width: 64px;
}
.github-corner:hover .octo-arm {
animation: a 0.56s ease-in-out;
}
.github-corner .octo-arm {
animation: none;
}
@keyframes a {
0%,
to {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
}
</style>
<a href="https://github.com/jiyongg-code" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
data-tooltip="Fork Me" data-position="left" data-delay="50">
<svg viewBox="0 0 250 250" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body"></path>
</svg>
</a>
</nav>
</header>
<script src="/libs/cryptojs/crypto-js.min.js"></script>
<script>
(function() {
let pwd = '';
if (pwd && pwd.length > 0) {
if (pwd !== CryptoJS.SHA256(prompt('请输入访问本文章的密码')).toString(CryptoJS.enc.Hex)) {
alert('密码错误,将返回主页!');
location.href = '/';
}
}
})();
</script>
<div class="bg-cover pd-header post-cover" style="background-image: url('/medias/featureimages/7.jpg')">
<div class="container">
<div class="row">
<div class="col s12 m12 l12">
<div class="brand">
<div class="description center-align post-title">
ccc
</div>
</div>
</div>
</div>
</div>
</div>
<main class="post-container content">
<link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
#articleContent h1::before,
#articleContent h2::before,
#articleContent h3::before,
#articleContent h4::before,
#articleContent h5::before,
#articleContent h6::before {
display: block;
content: " ";
height: 100px;
margin-top: -100px;
visibility: hidden;
}
#articleContent :focus {
outline: none;
}
.toc-fixed {
position: fixed;
top: 64px;
}
.toc-widget {
padding-left: 20px;
}
.toc-widget .toc-title {
margin: 35px 0 15px 0;
padding-left: 17px;
font-size: 1.5rem;
font-weight: bold;
line-height: 1.5rem;
}
.toc-widget ol {
padding: 0;
list-style: none;
}
#toc-content ol {
padding-left: 10px;
}
#toc-content ol li {
padding-left: 10px;
}
#toc-content .toc-link:hover {
color: #42b983;
font-weight: 700;
text-decoration: underline;
}
#toc-content .toc-link::before {
background-color: transparent;
max-height: 25px;
}
#toc-content .is-active-link {
color: #42b983;
}
#toc-content .is-active-link::before {
background-color: #42b983;
}
#floating-toc-btn {
position: fixed;
right: 20px;
bottom: 76px;
padding-top: 15px;
margin-bottom: 0;
z-index: 998;
}
#floating-toc-btn .btn-floating {
width: 48px;
height: 48px;
}
#floating-toc-btn .btn-floating i {
line-height: 48px;
font-size: 1.4rem;
}
</style>
<div class="row">
<div id="main-content" class="col s12 m12 l9">
<!-- 文章内容详情 -->
<div id="artDetail">
<div class="card">
<div class="card-content article-info">
<div class="row tag-cate">
<div class="col s7">
<div class="article-tag">
<a href="/tags/学成在线项目/" target="_blank">
<span class="chip bg-color">学成在线项目</span>
</a>
</div>
</div>
<div class="col s5 right-align">
<div class="post-cate">
<i class="fa fa-bookmark fa-fw icon-category"></i>
<a href="/categories/项目/" class="post-category" target="_blank">
项目
</a>
</div>
</div>
</div>
<div class="post-info">
<div class="post-date info-break-policy">
<i class="fa fa-calendar-minus-o fa-fw"></i>发布日期:
2023-03-27
</div>
<div class="post-author info-break-policy">
<i class="fa fa-user-o fa-fw"></i>作者:
jiyonggang
</div>
<div class="info-break-policy">
<i class="fa fa-file-word-o fa-fw"></i>文章字数:
22.4k
</div>
<div class="info-break-policy">
<i class="fa fa-clock-o fa-fw"></i>阅读时长:
92 分
</div>
<div id="busuanzi_container_page_pv" class="info-break-policy">
<i class="fa fa-eye fa-fw"></i>阅读次数:
<span id="busuanzi_value_page_pv"></span>
</div>
</div>
</div>
<hr class="clearfix">
<div class="card-content article-card-content">
<div id="articleContent">
<h1 id="学成在线"><a href="#学成在线" class="headerlink" title="学成在线"></a>学成在线</h1><h1 id="项目介绍"><a href="#项目介绍" class="headerlink" title="项目介绍"></a>项目介绍</h1><p>本项目包括了用户端、机构端、运营端。 核心模块包括:内容管理、媒资管理、课程搜索、订单支付、选课管理、认证授权等。</p>
<p><img src="/xuecheng/image-20230327170609556.png" alt="image-20230327170609556"></p>
<p>本项目采用前后端分离架构,后端采用SpringBoot、SpringCloud技术栈开发,数据库使用了MySQL, 还使用的Redis、消息队列、分布式文件系统、Elasticsearch等中间件系统。 划分的微服务包括:内容管理服务、媒资管理服务、搜索服务、订单支付服务、 学习中心服务、系统管 理服务、认证授权服务、网关服务、注册中心服务、配置中心服务等。</p>
<h1 id="项目的技术架构"><a href="#项目的技术架构" class="headerlink" title="项目的技术架构"></a>项目的技术架构</h1><h2 id="项目技术架构"><a href="#项目技术架构" class="headerlink" title="项目技术架构"></a>项目技术架构</h2><p>学成在线项目采用当前流行的前后端分离架构开发,由以下流程来构成:用户层、CDN内容分发和加 速、负载均衡、UI层、微服务层、数据层。</p>
<p><img src="/xuecheng/image-20230327170755746.png" alt="image-20230327170755746"></p>
<p>技术架构列表</p>
<table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>功能描述</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>用户层</td>
<td>用户层描述了本系统所支持的用户类型包括:pc用户、app用户、h5用户。pc用 户通过浏览器访问系统、app用户通过android、ios手机访问系统,H5用户通过 h5页面访问系统。</td>
</tr>
<tr>
<td>2</td>
<td>CDN</td>
<td>CDN全称Content Delivery Network,即内容分发网络,本系统所有静态资源全 部通过CDN加速来提高访问速度。系统静态资源包括:html页面、js文件、css文 件、image图片、pdf和ppt及doc教学文档、video视频等。</td>
</tr>
<tr>
<td>3</td>
<td>负载均衡</td>
<td>系统的CDN层、UI层、服务层及数据层均设置了负载均衡服务,上图仅在UI层前 边标注了负载均衡。 每一层的负载均衡会根据系统的需求来确定负载均衡器的类 型,系统支持4层负载均衡+7层负载均衡结合的方式,4层负载均衡是指在网络传 输层进行流程转发,根据IP和端口进行转发,7层负载均衡完成HTTP协议负载均 衡及反向代理的功能,根据url进行请求转发。</td>
</tr>
<tr>
<td>4</td>
<td>UI层</td>
<td>UI层描述了系统向pc用户、app用户、h5用户提供的产品界面。根据系统功能模 块特点确定了UI层包括如下产品界面类型: 1)面向pc用户的门户系统、学习中 心系统、教学管理系统、系统管理中心。 2)面向h5用户的门户系统、学习中心 系统。 3)面向app用户的门户系统、学习中心系统。</td>
</tr>
<tr>
<td>5</td>
<td>微服务层</td>
<td>微服务层将系统服务分类三类:业务服务、基础服务、第三方代理服务。 业务服 务:主要为学成在线核心业务提供服务,并与数据层进行交互获得数据。 基础服 务:主要管理学成在线系统运行所需的配置、日志、任务调度、短信等系统级别 的服务。 第三方代理服务:系统接入第三方服务完成业务的对接,例如认证、支 付、视频点播/直播、用户认证和授权。</td>
</tr>
<tr>
<td>6</td>
<td>数据层</td>
<td>数据层描述了系统的数据存储的内容类型,关系性数据库:持久化的业务数据使 用MySQL。 消息队列:存储系统服务间通信的消息,本身提供消息存取服务,与 微服务层的系统服务连接。 索引库:存储课程信息的索引信息,本身提供索引维 护及搜索的服务,与微服务层的系统服务连接。 缓存:作为系统的缓存服务,作 为微服务的缓存数据便于查询。 文件存储:提供系统静态资源文件的分布式存储 服务,文件存储服务器作为CDN服务器的数据来源,CDN上的静态资源将最终在 文件存储服务器上保存多份。</td>
</tr>
</tbody></table>
<ol>
<li>用户可以通过pc、手机等客户端访问系统进行在线学习。 </li>
<li>系统应用CDN技术,对一些图片、CSS、视频等资源从CDN调度访问。</li>
<li>所有的请求全部经过负载均衡器。 </li>
<li>对于PC、H5等客户端请求,首先请求UI层,渲染用户界面。 5</li>
<li>客户端UI请求服务层获取进行具体的业务操作。 6. 服务层将数据持久化到数据库。</li>
</ol>
<h2 id="项目技术栈"><a href="#项目技术栈" class="headerlink" title="项目技术栈"></a>项目技术栈</h2><p><img src="/xuecheng/image-20230327172852937.png" alt="image-20230327172852937"></p>
<h1 id="登录权限模块"><a href="#登录权限模块" class="headerlink" title="登录权限模块"></a>登录权限模块</h1><p>这个模块整合了Spring security+Oauth2+JWT搭建认证服务器,网关,微服务之间权限认证及授权。</p>
<p>授权码模式,请求了如下地址<br><a href="http://127.0.0.1:8080/oauth/authorize" target="_blank" rel="noopener">http://127.0.0.1:8080/oauth/authorize</a></p>
<p><a href="http://127.0.0.1:8080/oauth/token" target="_blank" rel="noopener">http://127.0.0.1:8080/oauth/token</a></p>
<p>/oauth/authorize 是OAuth 2.0授权码授权流程中的第一步,用于获取用户的授权,在该步骤中,用户会被重定向到授权服务器,要求用户授权客户端访问受保护的资源。</p>
<p>/oauth/token 是OAuth 2.0授权码授权流程的第二步,用于交换授权码和访问令牌。在该步骤中,客户端使用授权码向授权服务器请求访问令牌。授权服务器验证授权码的有效性,并返回访问令牌给客户端。</p>
<p>简化模式<br><a href="http://127.0.0.1:8080/oauth/authorize" target="_blank" rel="noopener">http://127.0.0.1:8080/oauth/authorize</a></p>
<p>客户端模式<br><a href="http://127.0.0.1:8080/oauth/token" target="_blank" rel="noopener">http://127.0.0.1:8080/oauth/token</a><br>密码模式<br><a href="http://127.0.0.1:8080/oauth/token" target="_blank" rel="noopener">http://127.0.0.1:8080/oauth/token</a></p>
<p>在用密码登录的时候是用的密码模式</p>
<p>前端请求的是Spring Security Oauth2默认的端口/oauth/token</p>
<p>/api/auth/oauth/token</p>
<p>auth/是服务地址</p>
<h2 id="模块需求分析"><a href="#模块需求分析" class="headerlink" title="模块需求分析"></a>模块需求分析</h2><h3 id="什么是认证授权"><a href="#什么是认证授权" class="headerlink" title="什么是认证授权"></a>什么是认证授权</h3><p>认证授权模块实现平台所有用户的身份认证与用户授权功能。</p>
<p>什么是用户身份认证?</p>
<p> 用户身份认证即用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问。常见的用户身份认证的表现形式有:用户名密码登录,微信扫码等方式。</p>
<p>项目包括学生、学习机构的老师、平台运营人员三类用户,不管哪一类用户在访问项目受保护资源时都需要进行身份认证。比如:发布课程操作,需要学习机构的老师首先登录系统成功,然后再执行发布课程操作。创建订单,需要学生用户首先登录系统,才可以创建订单。如下图:</p>
<p><img src="/xuecheng/clip_image002-1680277539941.gif" alt="img"></p>
<p>什么是用户授权?</p>
<p> 用户认证通过后去访问系统的资源,系统会判断用户是否拥有访问资源的权限,只允许访问有权限的系统资源,没有权限的资源将无法访问,这个过程叫用户授权。比如:用户去发布课程,系统首先进行用户身份认证,认证通过后继续判断用户是否有发布课程的权限,如果没有权限则拒绝继续访问系统,如果有权限则继续发布课程。如下图:</p>
<p><img src="/xuecheng/clip_image004-1680277539942.gif" alt="img"></p>
<h2 id="业务流程"><a href="#业务流程" class="headerlink" title="业务流程"></a>业务流程</h2><h4 id="统一认证"><a href="#统一认证" class="headerlink" title="统一认证"></a>统一认证</h4><p>用户输入账号和密码提交认证,认证通过则继续操作。</p>
<p>项目由统一认证服务受理用户的认证请求,如下图:</p>
<p><img src="/xuecheng/image-20230331234646982.png" alt="image-20230331234646982"></p>
<p>认证通过由认证服务向给用户颁发令牌,相当于访问系统的通行证,用户拿着令牌去访问系统的资源。</p>
<h4 id="单点登录"><a href="#单点登录" class="headerlink" title="单点登录"></a>单点登录</h4><p>本项目基于微服务架构构建,微服务包括:内容管理服务、媒资管理服务、学习中心服务、系统管理服务等,为了提高用户体验性,用户只需要认证一次便可以在多个拥有访问权限的系统中访问,这个功能叫做单点登录。</p>
<p>引用百度百科:单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。</p>
<p>如下图,用户只需要认证一次,便可以在多个拥有访问权限的系统中访问。</p>
<p><img src="/xuecheng/image-20230331235053961.png" alt="image-20230331235053961"></p>
<h4 id="第三方认证"><a href="#第三方认证" class="headerlink" title="第三方认证"></a><strong>第三方认证</strong></h4><p>为了提高用户体验,很多网站有扫码登录的功能,如:微信扫码登录、QQ扫码登录等。扫码登录的好处是用户不用输入账号和密码,操作简便,另外一个好处就是有利于用户信息的共享,互联网的优势就是资源共享,用户也是一种资源,对于一个新网站如果让用户去注册是很困难的,如果提供了微信扫码登录将省去用户注册的成本,是一种非常有效的推广手段。</p>
<p>微信扫码登录其中的原理正是使用了第三方认证,如下图:</p>
<p><img src="/xuecheng/image-20230331235118682.png" alt="image-20230331235118682"></p>
<p>整理授权的过程见下图所示:</p>
<p><img src="/xuecheng/image-20230331235230798.png" alt="image-20230331235230798"></p>
<h3 id="Spring-Security工作原理"><a href="#Spring-Security工作原理" class="headerlink" title="Spring Security工作原理"></a>Spring Security工作原理</h3><p>Spring Security所解决的问题就是<strong>安全访问控制</strong>,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。</p>
<p> 当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:</p>
<p><img src="/xuecheng/clip_image002-1680278032902.gif" alt="img"></p>
<p>FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的<strong>认证</strong>,也不直接处理用户的<strong>授权</strong>,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。</p>
<p>spring Security功能的实现主要是由一系列过滤器链相互配合完成。</p>
<p><img src="/xuecheng/clip_image004-1680278032902.gif" alt="img"></p>
<p>下面介绍过滤器链中主要的几个过滤器及其作用:</p>
<p><strong>SecurityContextPersistenceFilter</strong> 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;</p>
<p><strong>UsernamePasswordAuthenticationFilter</strong> 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;</p>
<p><strong>FilterSecurityInterceptor</strong> 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问。</p>
<p><strong>ExceptionTranslationFilter</strong> 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。</p>
<p>Spring Security的执行流程如下:</p>
<p><img src="/xuecheng/clip_image006-1680278032918.gif" alt="img"></p>
<ol>
<li><p>用户提交用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。</p>
</li>
<li><p>然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证</p>
</li>
<li><p>认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。</p>
</li>
<li><p>SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。</p>
</li>
<li><p>可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List<authenticationprovider>列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。</authenticationprovider></p>
</li>
</ol>
<h3 id="OAuth2"><a href="#OAuth2" class="headerlink" title="OAuth2"></a>OAuth2</h3><h4 id="OAuth2认证流程"><a href="#OAuth2认证流程" class="headerlink" title="OAuth2认证流程"></a>OAuth2认证流程</h4><p>微信扫码认证,这是一种第三方认证的方式,这种认证方式是基于OAuth2协议实现,</p>
<p>OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP、JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。互联网很多服务如Open API,很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。</p>
<p> Oauth协议目前发展到2.0版本,1.0版本过于复杂,2.0版本已得到广泛应用。</p>
<p>参考:<a href="https://baike.baidu.com/item/oAuth/7153134?fr=aladdin" target="_blank" rel="noopener">https://baike.baidu.com/item/oAuth/7153134?fr=aladdin</a></p>
<p>Oauth协议:<a href="https://tools.ietf.org/html/rfc6749" target="_blank" rel="noopener">https://tools.ietf.org/html/rfc6749</a></p>
<p>下边分析一个Oauth2认证的例子,黑马程序员网站使用微信认证扫码登录的过程:</p>
<p><img src="/xuecheng/1571688-20190506180810029-1179611880.png" alt="img"></p>
<p>Oauth2包括以下角色:</p>
<p>1、客户端</p>
<p>本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:手机客户端、浏览器等。</p>
<p>上边示例中黑马网站即为客户端,它需要通过浏览器打开。</p>
<p>2、资源拥有者</p>
<p>通常为用户,也可以是应用程序,即该资源的拥有者。</p>
<p>A表示 客户端请求资源拥有者授权。</p>
<p>B表示 资源拥有者授权客户端即黑马网站访问自己的用户信息。</p>
<p>3、授权服务器(也称认证服务器)</p>
<p>认证服务器对资源拥有者进行认证,还会对客户端进行认证并颁发令牌。</p>
<p>C 客户端即黑马网站携带授权码请求认证。</p>
<p>D认证通过颁发令牌。</p>
<p>4、资源服务器</p>
<p>存储资源的服务器。</p>
<p>E表示客户端即黑马网站携带令牌请求资源服务器获取资源。</p>
<p>F表示资源服务器校验令牌通过后提供受保护资源。</p>
<h4 id="OAuth2的授权模式"><a href="#OAuth2的授权模式" class="headerlink" title="OAuth2的授权模式"></a>OAuth2的授权模式</h4><p>Spring Security支持OAuth2认证,OAuth2提供授权码模式、密码模式、简化模式、客户端模式等四种授权模式,前边举的微信扫码登录的例子就是基于授权码模式,这四种模式中授权码模式和密码模式应用较多,本节使用Spring Security演示授权码模式、密码模式,其余两种请自行查阅相关资料。</p>
<p><strong>2.3.3.1</strong> <strong>授权码模式</strong></p>
<p>OAuth2的几个授权模式是根据不同的应用场景以不同的方式去获取令牌,最终目的是要获取认证服务颁发的令牌,最终通过令牌去获取资源。</p>
<p>授权码模式简单理解是使用授权码去获取令牌,要想获取令牌先要获取授权码,授权码的获取需要资源拥有者亲自授权同意才可以获取。</p>
<p>下图是授权码模式的交互图:</p>
<p><img src="/xuecheng/clip_image002-1680278892974.gif" alt="img"></p>
<h3 id="JWT"><a href="#JWT" class="headerlink" title="JWT"></a><strong>JWT</strong></h3><p><strong>2.4.1</strong> <strong>普通令牌的问题</strong></p>
<p>客户端申请到令牌,接下来客户端携带令牌去访问资源,到资源服务器将会校验令牌的合法性。</p>
<p>户端携带令牌访问资源服务获取资源。</p>
<p>2、资源服务远程请求认证服务校验令牌的合法性</p>
<p>3、如果令牌合法资源服务向客户端返回资源。</p>
<p>这里存在一个问题:</p>
<p>就是校验令牌需要远程请求认证服务,客户端的每次访问都会远程校验,执行性能低。</p>
<p>如果能够让资源服务自己校验令牌的合法性将省去远程请求认证服务的成本,提高了性能。</p>
<p>令牌采用JWT格式即可解决上边的问题,用户认证通过后会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。</p>
<h4 id="什么是JWT"><a href="#什么是JWT" class="headerlink" title="什么是JWT"></a>什么是JWT</h4><p>什么是JWT?</p>
<p>JSON Web Token(JWT)是一种使用JSON格式传递数据的网络令牌技术,它是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任,它可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止内容篡改。官网:<a href="https://jwt.io/" target="_blank" rel="noopener">https://jwt.io/</a></p>
<p>使用JWT可以实现无状态认证,什么是无状态认证?</p>
<p>传统的基于session的方式是有状态认证,用户登录成功将用户的身份信息存储在服务端,这样加大了服务端的存储压力,并且这种方式不适合在分布式系统中应用。</p>
<p>如下图,当用户访问应用服务,每个应用服务都会去服务器查看session信息,如果session中没有该用户则说明用户没有登录,此时就会重新认证,而解决这个问题的方法是Session复制、Session黏贴。</p>
<p><img src="/xuecheng/clip_image004-1680278950288.gif" alt="img"></p>
<p>如果是基于令牌技术在分布式系统中实现认证则服务端不用存储session,可以将用户身份信息存储在令牌中,用户认证通过后认证服务颁发令牌给用户,用户将令牌存储在客户端,去访问应用服务时携带令牌去访问,服务端从jwt解析出用户信息。这个过程就是无状态认证。</p>
<p><img src="/xuecheng/clip_image006-1680278950288.gif" alt="img"></p>
<p>JWT令牌的优点:</p>
<p>1、jwt基于json,非常方便解析。</p>
<p>2、可以在令牌中自定义丰富的内容,易扩展。</p>
<p>3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。</p>
<p>4、资源服务使用JWT可不依赖认证服务即可完成授权。</p>
<p>缺点:</p>
<p>1、JWT令牌较长,占存储空间比较大。</p>
<p>下边是一个JWT令牌的示例:</p>
<pre><code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQyNTQ2NzIsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6Ijg4OTEyYjJkLTVkMDUtNGMxNC1iYmMzLWZkZTk5NzdmZWJjNiIsImNsaWVudF9pZCI6ImMxIn0.wkDBL7roLrvdBG2oGnXeoXq-zZRgE9IVV2nxd-ez_oA </code></pre><p>JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz</p>
<ol>
<li><p>Header </p>
<p>头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)</p>
<p>一个例子如下:</p>
<p>下边是Header部分的内容</p>
</li>
</ol>
<pre><code> JSON { "alg": "HS256", "typ": "JWT" } </code></pre><p> 将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。</p>
<ol start="2">
<li><p>Payload</p>
<p>第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的信息字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。</p>
<p>此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。</p>
<p>最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。</p>
<p>一个例子:</p>
</li>
</ol>
<pre><code> JSON { "sub": "1234567890", "name": "456", "admin": true } </code></pre><ol start="3">
<li><p>Signature</p>
<p>第三部分是签名,此部分用于防止jwt内容被篡改。</p>
<p>这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明的签名算法进行签名。</p>
<p>一个例子:</p>
</li>
</ol>
<pre><code> JSON HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) </code></pre><p>base64UrlEncode(header):jwt令牌的第一部分。</p>
<p>base64UrlEncode(payload):jwt令牌的第二部分。</p>
<p>secret:签名所使用的密钥。</p>
<p>为什么JWT可以防止篡改?</p>
<p>第三部分使用签名算法对第一部分和第二部分的内容进行签名,常用的签名算法是 HS256,常见的还有md5,sha 等,签名算法需要使用密钥进行签名,密钥不对外公开,并且签名是不可逆的,如果第三方更改了内容那么服务器验证签名就会失败,要想保证验证签名正确必须保证内容、密钥与签名前一致。</p>
<p><img src="/xuecheng/clip_image008.gif" alt="img"></p>
<p>从上图可以看出认证服务和资源服务使用相同的密钥,这叫对称加密,对称加密效率高,如果一旦密钥泄露可以伪造jwt令牌。</p>
<p>JWT还可以使用非对称加密,认证服务自己保留私钥,将公钥下发给受信任的客户端、资源服务,公钥和私钥是配对的,成对的公钥和私钥才可以正常加密和解密,非对称加密效率低但相比对称加密非对称加密更安全一些。</p>
<h2 id="授权服务器"><a href="#授权服务器" class="headerlink" title="授权服务器"></a>授权服务器</h2><h3 id="授权服务器配置类"><a href="#授权服务器配置类" class="headerlink" title="授权服务器配置类"></a>授权服务器配置类</h3><p>新版这个写法过期了</p>
<pre><code> @Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Resource(name="authorizationServerTokenServicesCustom")
private AuthorizationServerTokenServices authorizationServerTokenServices;