SpringBoot整合Shiro框架

释放双眼,带上耳机,听听看~!

文章目录

  • 1.1、 配置项目环境
  • 1.2、定义用户认证授权微服务
  • 2.3、定义Shiro整合服务
  • 1.4、使用Redis进行数据缓存
  • 1.5、Thymeleaf整合Shiro标签

1.1、 配置项目环境

Shiro是现在最为流行的权限认证开发框架,与它齐名的只有最初的SpringSecurity(这个开发框架非常不好用,但是千万不要以为SpringSecurity没有用处,它在SpringCloud阶段将发挥重大作用)。但是现在如果要想整合Shiro开发框架有一点很遗憾,SpringBoot没有直接的配置支持,它不像整合所谓的Kafka、Redis、DataSource,也就是说如果要想整合Shiro开发框架那么就必须自己来进行所有的配置。

在整个的Shiro之中最为重要的部分:认真以及授权处理(Realm),在Realm里面实际上在开发之中所需要调用的业务方法只有两类:根据用户编号取得用户的完整信息,在认真通过之后根据用户编号获得用户对应的所有的角色以及权限信息。既然已经到了微架构的阶段,那么不得不去面对一个问题,对于这种用户的业务操作是放在WEB端还是单独提出来做成一个Rest服务?很明显,应该作为一个服务进行抽象出来,也就是说在整体的调用处理之中,Realm需要进行Rest服务调用(RestTemplate存在可以让整个的调用更加容易)。

那么按照如上的设计方案,现在的整体的项目里面认为应该包含有如下的几个开发模块:

  • microboot-shiro-api:应该提供有服务的VO类、各种加密处理的工具类;

  • microboot-shiro-memebre-provider:进行用户认证与授权REST服务提供,要暴露两个接口:用户信息获得、角色与权限信息获得;

  • microboot-shiro-web:主要进行Shiro的认证与授权检查处理。

SpringBoot整合Shiro框架
下面为开发做一些基础准备。

1、【microboot-shiro-member-provider】保存本次的数据库脚本:


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
1-- 删除数据库
2DROP DATABASE IF EXISTS mldn ;
3-- 创建数据库
4CREATE DATABASE mldn CHARACTER SET UTF8 ;
5-- 使用数据库
6USE mldn ;
7CREATE TABLE member(
8   mid         VARCHAR(50) ,
9   name        VARCHAR(50) ,
10  password    VARCHAR(32) ,
11  locked      INT ,
12  CONSTRAINT pk_mid PRIMARY KEY(mid)
13) ;
14CREATE TABLE role (
15  rid         VARCHAR(50)  ,
16  title       VARCHAR(50) ,
17  CONSTRAINT pk_rid PRIMARY KEY(rid)
18) ;
19CREATE TABLE action (
20  actid       VARCHAR(50) ,
21  title       VARCHAR(50) ,
22  rid         VARCHAR(50) ,
23  CONSTRAINT pk_actid PRIMARY KEY(actid)
24) ;
25CREATE TABLE member_role (
26  mid         VARCHAR(50) ,
27  rid         VARCHAR(50)
28) ;
29INSERT INTO member(mid,name,password,locked) VALUES ('mldnjava','mldn','2E866BF58289E01583AD418F486A69DF',0) ;
30INSERT INTO member(mid,name,password,locked) VALUES ('admin','admin','2E866BF58289E01583AD418F486A69DF',0) ;
31INSERT INTO role(rid,title) VALUES ('emp','雇员管理') ;
32INSERT INTO role(rid,title) VALUES ('dept','部门管理') ;
33INSERT INTO action(actid,title,rid) VALUES ('emp:add','雇员入职','emp') ;
34INSERT INTO action(actid,title,rid) VALUES ('emp:remove','雇员离职','emp') ;
35INSERT INTO action(actid,title,rid) VALUES ('emp:list','雇员列表','emp') ;
36INSERT INTO action(actid,title,rid) VALUES ('emp:edit','雇员编辑','emp') ;
37INSERT INTO action(actid,title,rid) VALUES ('dept:list','部门列表','dept') ;
38INSERT INTO action(actid,title,rid) VALUES ('dept:edit','部门编辑','dept') ;
39INSERT INTO member_role(mid,rid) VALUES ('mldnjava','emp') ;
40INSERT INTO member_role(mid,rid) VALUES ('admin','emp') ;
41INSERT INTO member_role(mid,rid) VALUES ('admin','dept') ;
42
43

2、【microboot-shiro-api】建立一个Member程序类,保存认证返回信息:

  • Shiro进行认证处理的时候是根据一个用户的编号获得用户对应的完整信息,而后再进行用户是否存在的判端、密码是否正确的判断、用户是否被锁定的判断


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1package cn.mldn.microboot.vo;
2
3import java.io.Serializable;
4
5public class Member implements Serializable {
6
7    private static final long serialVersionUID = 5879393512553198949L;
8
9    private String mid;
10    private String name;
11    private String password;
12    private String locked;
13
14......此处省略set、get和toString方法......
15
16

3、【microboot-shiro-api】既然有密码的加密处理,将之前所编写的Base64+MD5的加密程序工具配置到项目之中:

  • Base64加密:


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
1package cn.mldn.microboot.util.enctype;
2
3import java.util.Base64;
4
5public class PasswordUtil {
6   private static final String SEED  = "mldnjava" ;  // 该数据为种子数,如果要加密则需要使用Base64做多次迭代
7   private static final int NE_NUM = 3 ;   // 密码迭代处理3次
8   private PasswordUtil() {}
9   private static String createSeed() {    // 创建一个基于Base64的种子数
10      String str = SEED ;
11      for (int x = 0 ; x < NE_NUM ; x ++) {
12          str = Base64.getEncoder().encodeToString(str.getBytes()) ;
13      }
14      return str ;
15  }
16  /**
17   * 进行密码的处理操作
18   * @param password 用户输入的真实密码
19   * @return 与数据库保存匹配的加密的处理密码
20   */
21  public static String getPassword(String password) {
22      MD5Code md5 = new MD5Code() ;
23      String pass = "{" + password + ":" + createSeed() + "}";
24      for (int x = 0 ; x < NE_NUM ; x ++) {
25          pass = md5.getMD5ofStr(pass) ;
26      }
27      return pass ;
28  }
29}
30
31
  • MD5加密:


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
1package cn.mldn.microboot.util.enctype;
2
3public class MD5Code {
4   /*
5    * 下面这些S11-S44实际上是一个4*4的矩阵,在原始的C实现中是用#define 实现的, 这里把它们实现成为static
6    * final是表示了只读,切能在同一个进程空间内的多个 Instance间共享
7    */
8   static final int S11 = 7;
9
10  static final int S12 = 12;
11
12  static final int S13 = 17;
13
14  static final int S14 = 22;
15
16  static final int S21 = 5;
17
18  static final int S22 = 9;
19
20  static final int S23 = 14;
21
22  static final int S24 = 20;
23
24  static final int S31 = 4;
25
26  static final int S32 = 11;
27
28  static final int S33 = 16;
29
30  static final int S34 = 23;
31
32  static final int S41 = 6;
33
34  static final int S42 = 10;
35
36  static final int S43 = 15;
37
38  static final int S44 = 21;
39
40  static final byte[] PADDING = { -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
43          0, 0, 0, 0, 0, 0, 0 };
44
45  /*
46   * 下面的三个成员是MD5计算过程中用到的3个核心数据,在原始的C实现中 被定义到MD5_CTX结构中
47   */
48  private long[] state = new long[4];// state (ABCD)
49
50  private long[] count = new long[2];// number of bits, modulo 2^64 (lsb
51
52  // first)
53
54  private byte[] buffer = new byte[64]; // input buffer
55
56  /*
57   * digestHexStr是MD5的唯一一个公共成员,是最新一次计算结果的 16进制ASCII表示.
58   */
59
60  public String digestHexStr;
61
62  /*
63   * digest,是最新一次计算结果的2进制内部表示,表示128bit的MD5值.
64   */
65  private byte[] digest = new byte[16];
66
67  /*
68   * getMD5ofStr是类MD5最主要的公共方法,入口参数是你想要进行MD5变换的字符串
69   * 返回的是变换完的结果,这个结果是从公共成员digestHexStr取得的.
70   */
71  public String getMD5ofStr(String inbuf) {
72      md5Init();
73      md5Update(inbuf.getBytes(), inbuf.length());
74      md5Final();
75      digestHexStr = "";
76      for (int i = 0; i < 16; i++) {
77          digestHexStr += byteHEX(digest[i]);
78      }
79      return digestHexStr;
80  }
81
82  // 这是MD5这个类的标准构造函数,JavaBean要求有一个public的并且没有参数的构造函数
83  public MD5Code() {
84      md5Init();
85      return;
86  }
87
88  /* md5Init是一个初始化函数,初始化核心变量,装入标准的幻数 */
89  private void md5Init() {
90      count[0] = 0L;
91      count[1] = 0L;
92      // /* Load magic initialization constants.
93      state[0] = 0x67452301L;
94      state[1] = 0xefcdab89L;
95      state[2] = 0x98badcfeL;
96      state[3] = 0x10325476L;
97      return;
98  }
99
100 /*
101  * F, G, H ,I 是4个基本的MD5函数,在原始的MD5的C实现中,由于它们是
102  * 简单的位运算,可能出于效率的考虑把它们实现成了宏,在java中,我们把它们 实现成了private方法,名字保持了原来C中的。
103  */
104 private long F(long x, long y, long z) {
105     return (x & y) | ((~x) & z);
106 }
107
108 private long G(long x, long y, long z) {
109     return (x & z) | (y & (~z));
110 }
111
112 private long H(long x, long y, long z) {
113     return x ^ y ^ z;
114 }
115
116 private long I(long x, long y, long z) {
117     return y ^ (x | (~z));
118 }
119
120 /*
121  * FF,GG,HH和II将调用F,G,H,I进行近一步变换 FF, GG, HH, and II transformations for
122  * rounds 1, 2, 3, and 4. Rotation is separate from addition to prevent
123  * recomputation.
124  */
125 private long FF(long a, long b, long c, long d, long x, long s, long ac) {
126     a += F(b, c, d) + x + ac;
127     a = ((int) a << s) | ((int) a >>> (32 - s));
128     a += b;
129     return a;
130 }
131
132 private long GG(long a, long b, long c, long d, long x, long s, long ac) {
133     a += G(b, c, d) + x + ac;
134     a = ((int) a << s) | ((int) a >>> (32 - s));
135     a += b;
136     return a;
137 }
138
139 private long HH(long a, long b, long c, long d, long x, long s, long ac) {
140     a += H(b, c, d) + x + ac;
141     a = ((int) a << s) | ((int) a >>> (32 - s));
142     a += b;
143     return a;
144 }
145
146 private long II(long a, long b, long c, long d, long x, long s, long ac) {
147     a += I(b, c, d) + x + ac;
148     a = ((int) a << s) | ((int) a >>> (32 - s));
149     a += b;
150     return a;
151 }
152
153 /*
154  * md5Update是MD5的主计算过程,inbuf是要变换的字节串,inputlen是长度,这个
155  * 函数由getMD5ofStr调用,调用之前需要调用md5init,因此把它设计成private的
156  */
157 private void md5Update(byte[] inbuf, int inputLen) {
158     int i, index, partLen;
159     byte[] block = new byte[64];
160     index = (int) (count[0] >>> 3) & 0x3F;
161     // /* Update number of bits */
162     if ((count[0] += (inputLen << 3)) < (inputLen << 3))
163         count[1]++;
164     count[1] += (inputLen >>> 29);
165     partLen = 64 - index;
166     // Transform as many times as possible.
167     if (inputLen >= partLen) {
168         md5Memcpy(buffer, inbuf, index, 0, partLen);
169         md5Transform(buffer);
170         for (i = partLen; i + 63 < inputLen; i += 64) {
171             md5Memcpy(block, inbuf, 0, i, 64);
172             md5Transform(block);
173         }
174         index = 0;
175     } else
176         i = 0;
177     // /* Buffer remaining input */
178     md5Memcpy(buffer, inbuf, index, i, inputLen - i);
179 }
180
181 /*
182  * md5Final整理和填写输出结果
183  */
184 private void md5Final() {
185     byte[] bits = new byte[8];
186     int index, padLen;
187     // /* Save number of bits */
188     Encode(bits, count, 8);
189     // /* Pad out to 56 mod 64.
190     index = (int) (count[0] >>> 3) & 0x3f;
191     padLen = (index < 56) ? (56 - index) : (120 - index);
192     md5Update(PADDING, padLen);
193     // /* Append length (before padding) */
194     md5Update(bits, 8);
195     // /* Store state in digest */
196     Encode(digest, state, 16);
197 }
198
199 /*
200  * md5Memcpy是一个内部使用的byte数组的块拷贝函数,从input的inpos开始把len长度的
201  * 字节拷贝到output的outpos位置开始
202  */
203 private void md5Memcpy(byte[] output, byte[] input, int outpos, int inpos,
204         int len) {
205     int i;
206     for (i = 0; i < len; i++)
207         output[outpos + i] = input[inpos + i];
208 }
209
210 /*
211  * md5Transform是MD5核心变换程序,有md5Update调用,block是分块的原始字节
212  */
213 private void md5Transform(byte block[]) {
214     long a = state[0], b = state[1], c = state[2], d = state[3];
215     long[] x = new long[16];
216     Decode(x, block, 64);
217     /* Round 1 */
218     a = FF(a, b, c, d, x[0], S11, 0xd76aa478L); /* 1 */
219     d = FF(d, a, b, c, x[1], S12, 0xe8c7b756L); /* 2 */
220     c = FF(c, d, a, b, x[2], S13, 0x242070dbL); /* 3 */
221     b = FF(b, c, d, a, x[3], S14, 0xc1bdceeeL); /* 4 */
222     a = FF(a, b, c, d, x[4], S11, 0xf57c0fafL); /* 5 */
223     d = FF(d, a, b, c, x[5], S12, 0x4787c62aL); /* 6 */
224     c = FF(c, d, a, b, x[6], S13, 0xa8304613L); /* 7 */
225     b = FF(b, c, d, a, x[7], S14, 0xfd469501L); /* 8 */
226     a = FF(a, b, c, d, x[8], S11, 0x698098d8L); /* 9 */
227     d = FF(d, a, b, c, x[9], S12, 0x8b44f7afL); /* 10 */
228     c = FF(c, d, a, b, x[10], S13, 0xffff5bb1L); /* 11 */
229     b = FF(b, c, d, a, x[11], S14, 0x895cd7beL); /* 12 */
230     a = FF(a, b, c, d, x[12], S11, 0x6b901122L); /* 13 */
231     d = FF(d, a, b, c, x[13], S12, 0xfd987193L); /* 14 */
232     c = FF(c, d, a, b, x[14], S13, 0xa679438eL); /* 15 */
233     b = FF(b, c, d, a, x[15], S14, 0x49b40821L); /* 16 */
234     /* Round 2 */
235     a = GG(a, b, c, d, x[1], S21, 0xf61e2562L); /* 17 */
236     d = GG(d, a, b, c, x[6], S22, 0xc040b340L); /* 18 */
237     c = GG(c, d, a, b, x[11], S23, 0x265e5a51L); /* 19 */
238     b = GG(b, c, d, a, x[0], S24, 0xe9b6c7aaL); /* 20 */
239     a = GG(a, b, c, d, x[5], S21, 0xd62f105dL); /* 21 */
240     d = GG(d, a, b, c, x[10], S22, 0x2441453L); /* 22 */
241     c = GG(c, d, a, b, x[15], S23, 0xd8a1e681L); /* 23 */
242     b = GG(b, c, d, a, x[4], S24, 0xe7d3fbc8L); /* 24 */
243     a = GG(a, b, c, d, x[9], S21, 0x21e1cde6L); /* 25 */
244     d = GG(d, a, b, c, x[14], S22, 0xc33707d6L); /* 26 */
245     c = GG(c, d, a, b, x[3], S23, 0xf4d50d87L); /* 27 */
246     b = GG(b, c, d, a, x[8], S24, 0x455a14edL); /* 28 */
247     a = GG(a, b, c, d, x[13], S21, 0xa9e3e905L); /* 29 */
248     d = GG(d, a, b, c, x[2], S22, 0xfcefa3f8L); /* 30 */
249     c = GG(c, d, a, b, x[7], S23, 0x676f02d9L); /* 31 */
250     b = GG(b, c, d, a, x[12], S24, 0x8d2a4c8aL); /* 32 */
251     /* Round 3 */
252     a = HH(a, b, c, d, x[5], S31, 0xfffa3942L); /* 33 */
253     d = HH(d, a, b, c, x[8], S32, 0x8771f681L); /* 34 */
254     c = HH(c, d, a, b, x[11], S33, 0x6d9d6122L); /* 35 */
255     b = HH(b, c, d, a, x[14], S34, 0xfde5380cL); /* 36 */
256     a = HH(a, b, c, d, x[1], S31, 0xa4beea44L); /* 37 */
257     d = HH(d, a, b, c, x[4], S32, 0x4bdecfa9L); /* 38 */
258     c = HH(c, d, a, b, x[7], S33, 0xf6bb4b60L); /* 39 */
259     b = HH(b, c, d, a, x[10], S34, 0xbebfbc70L); /* 40 */
260     a = HH(a, b, c, d, x[13], S31, 0x289b7ec6L); /* 41 */
261     d = HH(d, a, b, c, x[0], S32, 0xeaa127faL); /* 42 */
262     c = HH(c, d, a, b, x[3], S33, 0xd4ef3085L); /* 43 */
263     b = HH(b, c, d, a, x[6], S34, 0x4881d05L); /* 44 */
264     a = HH(a, b, c, d, x[9], S31, 0xd9d4d039L); /* 45 */
265     d = HH(d, a, b, c, x[12], S32, 0xe6db99e5L); /* 46 */
266     c = HH(c, d, a, b, x[15], S33, 0x1fa27cf8L); /* 47 */
267     b = HH(b, c, d, a, x[2], S34, 0xc4ac5665L); /* 48 */
268     /* Round 4 */
269     a = II(a, b, c, d, x[0], S41, 0xf4292244L); /* 49 */
270     d = II(d, a, b, c, x[7], S42, 0x432aff97L); /* 50 */
271     c = II(c, d, a, b, x[14], S43, 0xab9423a7L); /* 51 */
272     b = II(b, c, d, a, x[5], S44, 0xfc93a039L); /* 52 */
273     a = II(a, b, c, d, x[12], S41, 0x655b59c3L); /* 53 */
274     d = II(d, a, b, c, x[3], S42, 0x8f0ccc92L); /* 54 */
275     c = II(c, d, a, b, x[10], S43, 0xffeff47dL); /* 55 */
276     b = II(b, c, d, a, x[1], S44, 0x85845dd1L); /* 56 */
277     a = II(a, b, c, d, x[8], S41, 0x6fa87e4fL); /* 57 */
278     d = II(d, a, b, c, x[15], S42, 0xfe2ce6e0L); /* 58 */
279     c = II(c, d, a, b, x[6], S43, 0xa3014314L); /* 59 */
280     b = II(b, c, d, a, x[13], S44, 0x4e0811a1L); /* 60 */
281     a = II(a, b, c, d, x[4], S41, 0xf7537e82L); /* 61 */
282     d = II(d, a, b, c, x[11], S42, 0xbd3af235L); /* 62 */
283     c = II(c, d, a, b, x[2], S43, 0x2ad7d2bbL); /* 63 */
284     b = II(b, c, d, a, x[9], S44, 0xeb86d391L); /* 64 */
285     state[0] += a;
286     state[1] += b;
287     state[2] += c;
288     state[3] += d;
289 }
290
291 /*
292  * Encode把long数组按顺序拆成byte数组,因为java的long类型是64bit的, 只拆低32bit,以适应原始C实现的用途
293  */
294 private void Encode(byte[] output, long[] input, int len) {
295     int i, j;
296     for (i = 0, j = 0; j < len; i++, j += 4) {
297         output[j] = (byte) (input[i] & 0xffL);
298         output[j + 1] = (byte) ((input[i] >>> 8) & 0xffL);
299         output[j + 2] = (byte) ((input[i] >>> 16) & 0xffL);
300         output[j + 3] = (byte) ((input[i] >>> 24) & 0xffL);
301     }
302 }
303
304 /*
305  * Decode把byte数组按顺序合成成long数组,因为java的long类型是64bit的,
306  * 只合成低32bit,高32bit清零,以适应原始C实现的用途
307  */
308 private void Decode(long[] output, byte[] input, int len) {
309     int i, j;
310     for (i = 0, j = 0; j < len; i++, j += 4)
311         output[i] = b2iu(input[j]) | (b2iu(input[j + 1]) << 8)
312                 | (b2iu(input[j + 2]) << 16) | (b2iu(input[j + 3]) << 24);
313     return;
314 }
315
316 /*
317  * b2iu是我写的一个把byte按照不考虑正负号的原则的"升位"程序,因为java没有unsigned运算
318  */
319 public static long b2iu(byte b) {
320     return b < 0 ? b & 0x7F + 128 : b;
321 }
322
323 /*
324  * byteHEX(),用来把一个byte类型的数转换成十六进制的ASCII表示,
325  * 因为java中的byte的toString无法实现这一点,我们又没有C语言中的 sprintf(outbuf,"%02X",ib)
326  */
327 public static String byteHEX(byte ib) {
328     char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
329             'B', 'C', 'D', 'E', 'F' };
330     char[] ob = new char[2];
331     ob[0] = Digit[(ib >>> 4) & 0X0F];
332     ob[1] = Digit[ib & 0X0F];
333     String s = new String(ob);
334     return s;
335 }
336}
337
338

1.2、定义用户认证授权微服务

所谓的用户微服务指的是要求再【microboot- shiro-provider】 里面进行实现,该服务之中需要考虑如下几点:

  • 该服务需要进行数据库的开发,所以一定要进行数据库连接池的配置

  • 既然要进行微服务的编写,那么就一定需要提供有业务接口以及DAO实现子类,现在的实现将依靠MyBatis完成

  • 所以的微服务最终要通过 控制器的Rest进行发布处理

1、【microboot-shiro-member-provider】配置Druid数据库连接池

  • 需要修改pom.xml配置文件,为项目的整合添加相关的支持包:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1<dependency>
2     <groupId>cn.mldn</groupId>
3     <artifactId>microboot-shiro-api</artifactId>
4     <version>0.0.1-SNAPSHOT</version>
5</dependency>
6      <dependency>
7      <groupId>mysql</groupId>
8      <artifactId>mysql-connector-java</artifactId>
9</dependency>
10<dependency>
11      <groupId>com.alibaba</groupId>
12      <artifactId>druid</artifactId>
13      </dependency>
14<dependency>
15       <groupId>ch.qos.logback</groupId>
16       <artifactId>logback-core</artifactId>
17</dependency>
18       <dependency>
19       <groupId>org.mybatis.spring.boot</groupId>
20       <artifactId>mybatis-spring-boot-starter</artifactId>
21</dependency>
22
23

2、【microboot-shiro-member-provider】建立几个DAO接口:

  • 提供用户认证的DAO接口:IMemberDao


1
2
3
4
5
6
7
8
9
10
11
12
1package cn.mldn.microboot.microbootshiroprovider.dao;
2import cn.mldn.microboot.vo.Member;
3import org.apache.ibatis.annotations.Mapper;
4
5@Mapper
6public interface IMemberDAO {
7    
8    public Member findById(String mid);
9    
10}
11
12
  • 提供角色检查的IRoleDao接口:


1
2
3
4
5
6
7
8
9
10
11
12
13
1package cn.mldn.microboot.microbootshiroprovider.dao;
2
3import org.apache.ibatis.annotations.Mapper;
4import java.util.Set;
5
6@Mapper
7public interface IRoleDAO {
8
9    public Set<String> findAllRoleByMember(String mid);
10    
11}
12
13
  • 提供所有权限检测的IActionDAO接口:


1
2
3
4
5
6
7
8
9
10
11
1package cn.mldn.microboot.microbootshiroprovider.dao;
2
3import java.util.Set;
4
5public interface IActionDAO {
6
7    public Set<String> findAllActionByMember(String mid);
8
9}
10
11

3、【microboot-shiro-member-provider】将mybatis的配置文件拷贝到项目的【src/main/resources】中:

  • src/main/resources/mybatis/mybatis.cfg.xml文件配置:


1
2
3
4
5
6
7
8
9
10
11
1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE configuration  
3    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
4    "http://mybatis.org/dtd/mybatis-3-config.dtd">
5<configuration>  <!-- 进行Mybatis的相应的环境的属性定义 -->
6   <settings>    <!-- 在本项目之中开启二级缓存 -->
7       <setting name="cacheEnabled" value="true"/>
8   </settings>
9</configuration>
10
11
  • 配置src/main/resources/mybatis/mapper/cn/mldn/Member.xml配置文件:


1
2
3
4
5
6
7
8
9
10
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4<mapper namespace="cn.mldn.microboot.dao.IMemberDAO">
5    <select id="findById" parameterType="String" resultType="Member">
6       SELECT mid,name,password,locked FROM member WHERE mid=#{mid} ;
7   </select>
8</mapper>
9
10
  • 配置src/main/resources/mybatis/mapper/cn/mldn/Role.xml配置文件:


1
2
3
4
5
6
7
8
9
10
11
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4<mapper namespace="cn.mldn.microboot.dao.IRoleDAO">
5   <select id="findAllRoleByMember" parameterType="String" resultType="String">
6       SELECT rid FROM role WHERE rid IN (
7           SELECT rid FROM member_role WHERE mid=#{mid}) ;
8   </select>
9</mapper>
10
11
  • 配置src/main/resources/mybatis/mapper/cn/mldn/Role.xml配置文件:


1
2
3
4
5
6
7
8
9
10
11
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4<mapper namespace="cn.mldn.microboot.dao.IActionDAO">
5   <select id="findAllActionByMember" parameterType="String" resultType="String">
6       SELECT actid FROM action WHERE rid IN (
7           SELECT rid FROM member_role WHERE mid=#{mid}) ;
8   </select>
9</mapper>  
10
11

4、【microboot-shiro-member-provider】修改application.yml配置文件:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1server:
2  port: 8001
3mybatis:
4  config-location: classpath:mybatis/batis.cfg.xml  # mybaits配置文件所在路径
5  type-aliases-package: cn.mldn.microboot.vo        # 定义所有操作类的别名所在包
6  mapper-locations:                                 # 所有的mapper映射文件
7    - classpath:mybatis/mapper/**/*.xml
8spring:
9  datasource:
10    type: com.alibaba.druid.pool.DruidDataSource
11    driver-class-name: org.gjt.mm.mysql.Driver
12    url: jdbc:mysql://localhost:3306/mldn
13    data-username: root
14    data-password: 123456
15    dbcp2:
16      min-idle: 5                                   # 进行数据库连接池的配置
17      initial-size: 5                               # 数据库连接池的最新维持连接数
18      max-total: 5                                  # 初始化提供的连接数
19      max-wait-millis: 200                          # 等待连接获取的最大超时时间
20
21

5、【microboot-shiro-member-provider】定义IMemberService业务接口和实现类:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1package cn.mldn.microboot.service;
2
3import cn.mldn.microboot.vo.Member;
4
5import java.util.Map;
6import java.util.Set;
7
8public interface IMemberSerivce {
9
10    public Member get(String mid);
11
12    public Map<String, Set<String>> listAuthByMember(String mid);
13}
14
15

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
1package cn.mldn.microboot.service.impl;
2
3import cn.mldn.microboot.dao.IActionDAO;
4import cn.mldn.microboot.dao.IMemberDAO;
5import cn.mldn.microboot.dao.IRoleDAO;
6import cn.mldn.microboot.service.IMemberSerivce;
7import cn.mldn.microboot.vo.Member;
8import org.springframework.stereotype.Service;
9
10import javax.annotation.Resource;
11import java.util.HashMap;
12import java.util.Map;
13import java.util.Set;
14
15@Service
16public class MemberServiceImpl implements IMemberSerivce {
17
18    @Resource
19    private IMemberDAO memberDAO;
20    @Resource
21    private IRoleDAO roleDAO;
22    @Resource
23    private IActionDAO actionDAO;
24
25    @Override
26    public Member get(String mid) {
27        return this.memberDAO.findById(mid);
28    }
29
30    @Override
31    public Map<String, Set<String>> listAuthByMember(String mid) {
32        Map<String, Set<String>> map = new HashMap<>();
33        map.put("allRoles", this.roleDAO.findAllRoleByMember(mid));
34        map.put("allAction", this.actionDAO.findAllActionByMember(mid));
35        return map;
36    }
37}
38
39

6、【microboot-shiro-member-provider】编写业务层功能测试类:


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
1package cn.mldn.microboot;
2
3import javax.annotation.Resource;
4
5import org.junit.Test;
6import org.junit.runner.RunWith;
7import org.springframework.boot.test.context.SpringBootTest;
8import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
9import org.springframework.test.context.web.WebAppConfiguration;
10
11import cn.mldn.microboot.service.IMemberService;
12
13@SpringBootTest(classes = StartSpringBootMain.class)
14@RunWith(SpringJUnit4ClassRunner.class)
15@WebAppConfiguration
16public class TestMemberService {
17  @Resource
18  private IMemberService memberService ;
19  @Test
20  public void testGet() {
21      System.out.println(this.memberService.get("admin"));
22  }
23  @Test
24  public void testAuth() {
25      System.out.println(this.memberService.listAuthByMember("admin"));
26  }
27}
28
29
30

7、【microboot-shiro-member-provider】进行控制层编写,控制层现在给出的一定是Rest服务:


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
1package cn.mldn.microboot.controller;
2
3import javax.annotation.Resource;
4
5import org.springframework.web.bind.annotation.RequestMapping;
6import org.springframework.web.bind.annotation.RequestMethod;
7import org.springframework.web.bind.annotation.RestController;
8
9import cn.mldn.microboot.service.IMemberService;
10
11@RestController
12public class MemberController {
13  @Resource
14  private IMemberService memberService;
15  @RequestMapping(value="/member/get")
16  public Object get(String mid) {
17      return this.memberService.get(mid) ;
18  }
19  @RequestMapping(value="/member/auth")
20  public Object auth(String mid) {
21      return this.memberService.listAuthByMember(mid) ;
22  }
23}
24
25

8、【microboot-shiro-member-provider】编写控制层测试,如果要访问Rest服务肯定要使用RestTemplate完成,这个类现在为了简单起见,直接进行对象实例化处理:


1
2
3
4
5
6
7
8
1修改MemberController中的:
2......
3@RequestMapping(value="/member/get",method=RequestMethod.POST)
4......
5@RequestMapping(value="/member/auth",method=RequestMethod.POST)
6......
7
8

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
1package cn.mldn.microboot;
2
3import java.util.HashSet;
4import java.util.List;
5import java.util.Map;
6import java.util.Set;
7
8import org.junit.Test;
9import org.junit.runner.RunWith;
10import org.springframework.boot.test.context.SpringBootTest;
11import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12import org.springframework.test.context.web.WebAppConfiguration;
13import org.springframework.web.client.RestTemplate;
14
15import cn.mldn.vo.Member;
16
17@SpringBootTest(classes = StartSpringBootMain.class)
18@RunWith(SpringJUnit4ClassRunner.class)
19@WebAppConfiguration
20public class TestMemberController {
21  private RestTemplate restTemplate = new RestTemplate() ;
22  @Test
23  public void testGet() {
24      String url = "http://localhost:8001/member/get?mid=admin" ;
25      Member vo = this.restTemplate.postForObject(url, null, Member.class) ;
26      System.out.println(vo);
27  }
28  @SuppressWarnings("unchecked")
29  @Test
30  public void testAuth() {
31      String url = "http://localhost:8001/member/auth?mid=admin" ;
32      Map<String,Object> map = this.restTemplate.postForObject(url, null, Map.class) ;
33      Set<String> allRoles = new HashSet<String>() ;
34      Set<String> allActions = new HashSet<String>() ;
35      allRoles.addAll((List<String>) map.get("allRoles"));
36      allActions.addAll((List<String>) map.get("allActions")) ;
37      System.out.println("【角色】" + allRoles);
38      System.out.println("【权限】" + allActions);
39  }
40}
41
42

那么此时一个专门进行用户认证以及权限检测的微服务开发完成。

2.3、定义Shiro整合服务

在本次项目之中WEB模块为【microboot-shiro-web】,很明显对于WEB模块之中必须要求调用用户认证与授权微服务(Realm),而后需要进行各种依赖包的配置(Shiro)、考虑到各种缓存的问题、认证与授权检测问题。

1、【microboot-shiro-web】修改pom.xml配置文件,追加Shiro的相关依赖程序包:


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
1<dependency>
2           <groupId>cn.mldn</groupId>
3           <artifactId>microboot-shiro-api</artifactId>
4           <version>0.0.1-SNAPSHOT</version>
5       </dependency>
6
7       <dependency>
8           <groupId>org.apache.shiro</groupId>
9           <artifactId>shiro-spring</artifactId>
10          <version>1.4.0</version>
11      </dependency>
12
13      <dependency>
14          <groupId>org.apache.shiro</groupId>
15          <artifactId>shiro-core</artifactId>
16          <version>1.4.0</version>
17      </dependency>
18
19      <dependency>
20          <groupId>org.apache.shiro</groupId>
21          <artifactId>shiro-ehcache</artifactId>
22          <version>1.4.0</version>
23      </dependency>
24
25      <dependency>
26          <groupId>org.apache.shiro</groupId>
27          <artifactId>shiro-quartz</artifactId>
28          <version>1.4.0</version>
29      </dependency>
30
31      <dependency>
32          <groupId>org.apache.shiro</groupId>
33          <artifactId>shiro-web</artifactId>
34          <version>1.4.0</version>
35      </dependency>
36
37

2、【microboot-shiro-web】建立一个RestTemplate的配置类对象:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1package cn.mldn.microboot.config;
2
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5
6@Configuration
7public class RestTemplate {
8    
9    @Bean
10    public RestTemplate getRestTemplate(){
11        return new RestTemplate();
12    }
13}
14
15

3、【microboot-shiro-web】Shiro之中所有认证和授权的处理都在Realm之中定义了:


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
1package cn.mldn.microboot.realm;
2
3import cn.mldn.util.enctype.PasswordUtil;
4import cn.mldn.vo.Member;
5import groovy.util.IFileNameFinder;
6import org.apache.shiro.SecurityUtils;
7import org.apache.shiro.authc.*;
8import org.apache.shiro.authz.AuthorizationInfo;
9import org.apache.shiro.authz.SimpleAuthorizationInfo;
10import org.apache.shiro.realm.AuthorizingRealm;
11import org.apache.shiro.subject.PrincipalCollection;
12import org.springframework.web.client.RestTemplate;
13
14import javax.annotation.Resource;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20public class MemberRealm extends AuthorizingRealm {
21
22    @Resource
23    private RestTemplate restTemplate;
24
25    @Override
26    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
27        System.out.println("============= 1、进行认证操作处理 =============");
28        String mid = token.getPrincipal().toString(); // 用户名
29        // 取得用户名之后就需要通过业务层获取用户对象以确定该用户名是否可用
30        String url = "http://localhost:8001/member/get?mid="+ mid ;
31        Member member = this.restTemplate.postForObject(url, null, Member.class);
32        // 表示该用户信息不存在,不存在则应该抛出一个异常
33        if (member == null){
34            throw new UnknownAccountException("搞什么搞,用户名不存在!");
35        }
36        // 用户名如果存在了,那么就需要缺点密码是否正确
37        String password = PasswordUtil.getPassword(new String((char[]) token.getCredentials()));
38        if (!password.equals(member.getPassword())){
39            throw new IncorrectCredentialsException("密码都记不住,去死吧!");
40        }
41        // 随后还需要考虑用户被锁定的问题
42        if (member.getLocked().equals(1)){ //1表示非0,非0就是true
43            throw new LockedAccountException("被锁了,求解锁去吧!")
44        }
45        // 定义需要进行返回的操作数据信息项,返回的认证信息使用应该是密文
46        SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(
47                token.getPrincipal(), password, "memberRealm");
48        // 在认证完成之后可以直接取得用户所需要的信息内容,保存在Session之中
49        SecurityUtils.getSubject().getSession().setAttribute("name","我的名字");
50        return auth;
51    }
52
53    @Override
54    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
55        System.out.println("==========2、进行授权操作处理==============");
56        //该操作的主要目的是取得授权信息,说的直白一点就是角色和权限数据
57        SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
58        //执行到此方法的时候一定是已经进行过用户认证处理了(用户名和密码一定是正确的)
59        String mid = (String)principals.getPrimaryPrincipal(); //取得用户名
60        String url = "http://localhost:8001/member/auth?mid=" + mid;
61        Map<String, Object> map = this.restTemplate.postForObject(url, null, Map.class);
62        Set<String> allRoles = new HashSet<>();
63        Set<String> allActions = new HashSet<>();
64        allRoles.addAll((List<String> map.get("allRoles")));
65        allActions.addAll((List<String>) map.get("allActions"));
66        auth.setRoles(allRoles); //保存所有的角色
67        auth.setStringPermissions(allActions); //保存所有权限
68        return auth;
69    }
70}
71
72

4、【microboot-shiro-web】现在虽然准备好了Realm程序类,但是在整个Shiro进行整合处理的时候实际上需要编写大量的配置程序类,所以这个时候如果直接使用xml配置文件虽然可以,但是不标准,最好的做法是你将所有的xml配置项变为Bean配置:


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
1package cn.mldn.microboot.config;
2import java.util.HashMap;
3import java.util.Map;
4
5import javax.servlet.Filter;
6
7import org.apache.shiro.cache.ehcache.EhCacheManager;
8import org.apache.shiro.mgt.RememberMeManager;
9import org.apache.shiro.realm.Realm;
10import org.apache.shiro.session.mgt.SessionManager;
11import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
12import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
13import org.apache.shiro.session.mgt.eis.SessionDAO;
14import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
15import org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler;
16import org.apache.shiro.spring.LifecycleBeanPostProcessor;
17import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
18import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
19import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
20import org.apache.shiro.web.filter.authc.LogoutFilter;
21import org.apache.shiro.web.mgt.CookieRememberMeManager;
22import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
23import org.apache.shiro.web.servlet.SimpleCookie;
24import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
25import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
26import org.springframework.context.annotation.Bean;
27import org.springframework.context.annotation.Configuration;
28import org.springframework.context.annotation.DependsOn;
29
30import cn.mldn.microboot.realm.CustomerCredentialsMatcher;
31import cn.mldn.microboot.realm.MemberRealm;
32
33@Configuration
34public class ShiroConfig {
35  @Bean
36  public MemberRealm getRealm() {// 1、获取配置的Realm,之所以没使用注解配置,是因为此处需要考虑到加密处理
37      MemberRealm realm = new MemberRealm();
38      realm.setCredentialsMatcher(new CustomerCredentialsMatcher()); 
39      return realm;
40  }
41
42  @Bean(name = "lifecycleBeanPostProcessor")
43  public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
44      return new LifecycleBeanPostProcessor();
45  }
46
47  @Bean
48  @DependsOn("lifecycleBeanPostProcessor")
49  public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
50      DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
51      daap.setProxyTargetClass(true);
52      return daap;
53  }
54
55  @Bean
56  public EhCacheManager getCacheManager() {// 2、缓存配置
57      EhCacheManager cacheManager = new EhCacheManager();
58      cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
59      return cacheManager;
60  }
61
62  @Bean
63  public SessionIdGenerator getSessionIdGenerator() { // 3
64      return new JavaUuidSessionIdGenerator();
65  }
66
67  @Bean
68  public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4
69      EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
70      sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
71      sessionDAO.setSessionIdGenerator(sessionIdGenerator);
72      return sessionDAO;
73  }
74
75  @Bean
76  public RememberMeManager getRememberManager() { // 5
77      CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
78      SimpleCookie cookie = new SimpleCookie("MLDNJAVA-RememberMe");
79      cookie.setHttpOnly(true);
80      cookie.setMaxAge(3600);
81      rememberMeManager.setCookie(cookie);
82      return rememberMeManager;
83  }
84
85  @Bean
86  public QuartzSessionValidationScheduler getQuartzSessionValidationScheduler() {
87      QuartzSessionValidationScheduler sessionValidationScheduler = new QuartzSessionValidationScheduler();
88      sessionValidationScheduler.setSessionValidationInterval(100000);
89      return sessionValidationScheduler;
90  }
91
92  @Bean
93  public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(
94          DefaultWebSecurityManager securityManager) {
95      AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
96      aasa.setSecurityManager(securityManager);
97      return aasa;
98  }
99
100 @Bean
101 public DefaultWebSessionManager getSessionManager(SessionDAO sessionDAO,
102         QuartzSessionValidationScheduler sessionValidationScheduler) { // 6
103     DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
104     sessionManager.setGlobalSessionTimeout(1000000);
105     sessionManager.setDeleteInvalidSessions(true);
106     sessionManager.setSessionValidationScheduler(sessionValidationScheduler);
107     sessionManager.setSessionValidationSchedulerEnabled(true);
108     sessionManager.setSessionDAO(sessionDAO);
109     SimpleCookie sessionIdCookie = new SimpleCookie("mldn-session-id");
110     sessionIdCookie.setHttpOnly(true);
111     sessionIdCookie.setMaxAge(-1);
112     sessionManager.setSessionIdCookie(sessionIdCookie);
113     sessionManager.setSessionIdCookieEnabled(true);
114     return sessionManager;
115 }
116
117 @Bean
118 public DefaultWebSecurityManager getSecurityManager(Realm memberRealm, EhCacheManager cacheManager,
119         SessionManager sessionManager, RememberMeManager rememberMeManager) {// 7
120     DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
121     securityManager.setRealm(memberRealm);
122     securityManager.setCacheManager(cacheManager);
123     securityManager.setSessionManager(sessionManager);
124     securityManager.setRememberMeManager(rememberMeManager);
125     return securityManager;
126 }
127
128 public FormAuthenticationFilter getLoginFilter() { // 在ShiroFilterFactoryBean中使用
129     FormAuthenticationFilter filter = new FormAuthenticationFilter();
130     filter.setUsernameParam("mid");
131     filter.setPasswordParam("password");
132     filter.setRememberMeParam("rememberMe");
133     filter.setLoginUrl("/loginPage"); // 登录提交页面
134     filter.setFailureKeyAttribute("error");
135     return filter;
136 }
137
138 public LogoutFilter getLogoutFilter() { // 在ShiroFilterFactoryBean中使用
139     LogoutFilter logoutFilter = new LogoutFilter();
140     logoutFilter.setRedirectUrl("/"); // 首页路径,登录注销后回到的页面
141     return logoutFilter;
142 }
143
144 @Bean
145 public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
146     ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
147     // 必须设置 SecurityManager
148     shiroFilterFactoryBean.setSecurityManager(securityManager);
149     shiroFilterFactoryBean.setLoginUrl("/loginPage"); // 设置登录页路径
150     shiroFilterFactoryBean.setSuccessUrl("/pages/hello"); // 设置跳转成功页
151     shiroFilterFactoryBean.setUnauthorizedUrl("/pages/unauthUrl");    // 授权错误页
152     Map<String, Filter> filters = new HashMap<String, Filter>();
153     filters.put("authc", this.getLoginFilter());
154     filters.put("logout", this.getLogoutFilter());
155     shiroFilterFactoryBean.setFilters(filters);
156     Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
157     filterChainDefinitionMap.put("/logout", "logout");
158     filterChainDefinitionMap.put("/loginPage", "authc");    // 定义内置登录处理
159     filterChainDefinitionMap.put("/pages/back/**", "authc");
160     filterChainDefinitionMap.put("/*", "anon");
161     shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
162     return shiroFilterFactoryBean;
163 }
164}
165
166
167

将ehcache.xml配置文件拷贝到scr/main/resources目录之中

5、【microboot-shiro-web】建立一个控制器


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1package cn.mldn.microboot.controller;
2
3import org.apache.shiro.authz.annotation.RequiresRoles;
4import org.springframework.web.bind.annotation.RequestMapping;
5import org.springframework.web.bind.annotation.RestController;
6
7@RestController
8public class DeptController {
9
10  @RequiresRoles("dept")
11  @RequestMapping("/pages/back/dept/get")
12  public String get(){
13      return "部门信息";
14  }
15}
16
17
18

6、【microboot-shiro-web】登录出错之后应该跑到表单上,所以建立一个MemberController,这个程序类负责此跳转处理:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
1package cn.mldn.microboot.controller;
2
3import org.springframework.stereotype.Controller;
4import org.springframework.web.bind.annotation.RequestMapping;
5
6@Controller
7public class MemberController {
8    @RequestMapping("/loginPage")
9    public String get(){
10        return "member_login";
11    }
12}
13
14

7、【microboot-shiro-web】建立一个member_login.html页面:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1<!DOCTYPE HTML>
2<html xmlns:th="http://www.thymeleaf.org">
3<head>
4   <title>SpringBoot模版渲染</title>
5   <script type="text/javascript" th:src="@{/js/main.js}"></script>
6   <link rel="icon" type="image/x-icon" href="/images/mldn.ico"/>
7   <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
8</head>
9<body>
10  <h1>用户登录表单、<span th:text="${error}"/></h1>
11  <form th:action="@{/loginPage}" method="post">
12      登录名:<input type="text" name="mid" value="mldnjava"/><br/>
13      密 码:<input type="text" name="password" value="hello"/><br/>
14      <input type="submit" value="登录"/>
15  </form>
16</body>
17</html>
18
19

此时实现了一个最基础的整合处理操作。

1.4、使用Redis进行数据缓存

现在是使用了EHCache缓存组件进行了缓存处理,而实际的项目之中往往会利用Redis实现缓存配置,那么下面将对程序进行一些修改。

1、【microboot-shiro-web】如果要进行缓存的使用,则首先一定要配置缓存处理类


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
1package cn.mldn.microboot.cache;
2
3import java.util.Collection;
4import java.util.HashSet;
5import java.util.Iterator;
6import java.util.Set;
7
8import org.apache.commons.logging.Log;
9import org.apache.commons.logging.LogFactory;
10import org.apache.shiro.cache.Cache;
11import org.apache.shiro.cache.CacheException;
12import org.springframework.dao.DataAccessException;
13import org.springframework.data.redis.connection.RedisConnection;
14import org.springframework.data.redis.core.RedisCallback;
15import org.springframework.data.redis.core.RedisTemplate;
16
17public class RedisCache<K, V> implements Cache<K, V> {
18  private Log log = LogFactory.getLog(RedisCache.class);
19  private RedisTemplate<String, Object> redisTempate; // 要提供有Redis处理工具类
20  public RedisCache(RedisTemplate<String, Object> redisTempate) {
21      this.redisTempate = redisTempate;
22  }
23  @Override
24  public V get(K key) throws CacheException {
25      log.info("### get() : K = " + key);
26      return (V) this.redisTempate.opsForValue().get(key.toString());
27  }
28  @Override
29  public V put(K key, V value) throws CacheException {
30      log.info("### put() : K = " + key + "、V = " + value);
31      this.redisTempate.opsForValue().set(key.toString(), value);
32      return value;
33  }
34
35  @Override
36  public V remove(K key) throws CacheException {
37      log.info("### remove() : K = " + key);
38      V val = this.get(key);
39      this.redisTempate.delete(key.toString());
40      return val;
41  }
42
43  @Override
44  public void clear() throws CacheException {
45      log.info("### clear()");
46      this.redisTempate.execute(new RedisCallback<Boolean>() {
47          @Override
48          public Boolean doInRedis(RedisConnection connection)
49                  throws DataAccessException {
50              connection.flushDb(); // 清空数据库
51              return true;
52          }
53      });
54  }
55
56  @Override
57  public int size() {
58      log.info("### size()");
59      return this.redisTempate.execute(new RedisCallback<Integer>() {
60          @Override
61          public Integer doInRedis(RedisConnection connection)
62                  throws DataAccessException {
63              return connection.keys("*".getBytes()).size();
64          }
65      });
66  }
67
68  @Override
69  public Set<K> keys() {
70      log.info("### keys()");
71      return this.redisTempate.execute(new RedisCallback<Set<K>>() {
72          @Override
73          public Set<K> doInRedis(RedisConnection connection)
74                  throws DataAccessException {
75              Set<K> set = new HashSet<K>();
76              Set<byte[]> keys = connection.keys("*".getBytes());
77              Iterator<byte[]> iter = keys.iterator();
78              while (iter.hasNext()) {
79                  set.add((K) iter.next());
80              }
81              return set;
82          }
83      });
84  }
85
86  @Override
87  public Collection<V> values() {
88      log.info("### values()");
89      return this.redisTempate.execute(new RedisCallback<Set<V>>() {
90          @Override
91          public Set<V> doInRedis(RedisConnection connection)
92                  throws DataAccessException {
93              Set<V> set = new HashSet<V>();
94              Set<byte[]> keys = connection.keys("*".getBytes());
95              Iterator<byte[]> iter = keys.iterator();
96              while (iter.hasNext()) {
97                  set.add((V) connection.get(iter.next()));
98              }
99              return set;
100         }
101     });
102 }
103}
104
105

2、【microboot-shiro-web】进行Redis缓存管理类的配置:


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
1package cn.mldn.microboot.cache;
2
3import java.util.concurrent.ConcurrentHashMap;
4import java.util.concurrent.ConcurrentMap;
5
6import javax.annotation.Resource;
7
8import org.apache.shiro.cache.Cache;
9import org.apache.shiro.cache.CacheException;
10import org.apache.shiro.cache.CacheManager;
11import org.springframework.data.redis.core.RedisTemplate;
12import org.springframework.stereotype.Component;
13@Component
14public class RedisCacheManager implements CacheManager {
15  // CacheManager负责所有数据的缓存,那么对于数据而言,应该保存在缓存里面
16  private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
17  @Resource
18  private RedisTemplate<String, Object> redisTemplate;
19  @Override
20  public Cache<Object, Object> getCache(String name) throws CacheException {
21      Cache<Object, Object> cache = this.caches.get(name); // 通过Map取得cache数据
22      if (cache == null) { // 当前的集合里面没有Cache的数据
23          cache = new RedisCache(this.redisTemplate); // 实例化一个新的Cache对象
24          this.caches.put(name, cache);
25      }
26      return cache;
27  }
28
29}
30
31

3、【microboot-shiro-web】配置一个Shiro中的Session管理操作


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
1package cn.mldn.microboot.session;
2
3import java.io.Serializable;
4
5import javax.annotation.Resource;
6
7import org.apache.commons.logging.Log;
8import org.apache.commons.logging.LogFactory;
9import org.apache.shiro.session.Session;
10import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
11// 此时的类将实现SessionDAO的改写
12import org.springframework.data.redis.core.RedisTemplate;
13public class RedisSessionDAO extends EnterpriseCacheSessionDAO {
14  private Log log = LogFactory.getLog(RedisSessionDAO.class);
15  @Resource
16  private RedisTemplate<String, Object> redisTempate; // 要提供有Redis处理工具类
17  @Override
18  protected Serializable doCreate(Session session) { // 创建Session,返回session id
19      log.info("*** doCreate : " + session);
20      Serializable sessionId = super.doCreate(session); // 创建sessionid
21      // 将当前创建好的Session的数据保存在Redis数据库里面
22      this.redisTempate.opsForValue().set(sessionId.toString(), session,
23              1800);
24      return sessionId;
25  }
26  @Override
27  protected Session doReadSession(Serializable sessionId) { // 根据session
28                                                              // id读取session数据
29      log.info("*** doReadSession : " + sessionId);
30      Session session = super.doReadSession(sessionId); // 读取Session数据
31      if (session == null) { // 现在没有读取到session数据,通过Redis读取
32          return (Session) this.redisTempate.opsForValue()
33                  .get(sessionId.toString());
34      }
35      return null;
36  }
37  @Override
38  protected void doUpdate(Session session) { // 实现Session更新,每次操作都要更新
39      log.info("*** doUpdate : " + session);
40      super.doUpdate(session);
41      if (session != null) {
42          this.redisTempate.opsForValue().set(session.getId().toString(),
43                  session, 1800);
44      }
45  }
46  @Override
47  protected void doDelete(Session session) { // session的删除处理
48      log.info("*** doDelete : " + session);
49      super.doDelete(session);
50      this.redisTempate.delete(session.getId().toString());
51  }
52}
53
54

4、【microboot-shiro-web】在当前的项目开发过程之中,配置shiro的Bean里面所使用的还是EHCache缓存组件,所以需要进行更换处理

  • 更换现在要使用的SessionDAO实现子类:


1
2
3
4
5
6
7
8
9
1   @Bean
2   public SessionDAO getSessionDAO(SessionIdGenerator sessionIdGenerator) { // 4
3       RedisSessionDAO sessionDAO = new RedisSessionDAO(); // 使用Redis进行Session管理
4       sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
5       sessionDAO.setSessionIdGenerator(sessionIdGenerator);
6       return sessionDAO;
7   }
8
9
  • 更换使用的缓存组件:


1
2
3
4
5
6
7
8
9
10
11
12
1   @Bean
2   public DefaultWebSecurityManager getSecurityManager(Realm memberRealm, RedisCacheManager cacheManager,
3           SessionManager sessionManager, RememberMeManager rememberMeManager) {// 7
4       DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
5       securityManager.setRealm(memberRealm);
6       securityManager.setCacheManager(cacheManager);
7       securityManager.setSessionManager(sessionManager);
8       securityManager.setRememberMeManager(rememberMeManager);
9       return securityManager;
10  }
11
12

5、【microboot-shiro-web】修改application.yml配置文件进行Redis配置:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1spring:
2  redis:
3    host: 192.168.68.166
4    port: 6379
5    password: mldnjava
6    timeout: 1000
7    database: 0
8    pool:
9      max-active: 10
10      max-idle: 8
11      min-idle: 2
12      max-wait: 100
13server:
14  port: 8080
15
16

同时修改pom.xml文件redis依赖支持包:


1
2
3
4
5
6
1<dependency>
2           <groupId>org.springframework.boot</groupId>
3           <artifactId>spring-boot-starter-data-redis</artifactId>
4       </dependency>
5
6

6、【microboot-shiro-web】建立一个RedisTemplate的配置程序类:


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
1package cn.mldn.microboot.config;
2
3import javax.annotation.Resource;
4
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.data.redis.connection.RedisConnectionFactory;
8import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
9import org.springframework.data.redis.core.RedisTemplate;
10import org.springframework.data.redis.serializer.StringRedisSerializer;
11
12import cn.mldn.microboot.util.RedisObjectSerializer;
13
14@Configuration
15public class RedisConfig {
16  @Resource
17  private JedisConnectionFactory jedisConnectionFactory ;
18  @Bean("shiroRedis")
19  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
20      RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
21      template.setConnectionFactory(this.jedisConnectionFactory);
22      template.setKeySerializer(new StringRedisSerializer());
23      template.setValueSerializer(new RedisObjectSerializer());
24      return template;
25  }
26}
27
28

7、【microboot-shiro-web】建立Redis序列号程序类:


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
1package cn.mldn.microboot.util;
2import org.springframework.core.convert.converter.Converter;
3import org.springframework.core.serializer.support.DeserializingConverter;
4import org.springframework.core.serializer.support.SerializingConverter;
5import org.springframework.data.redis.serializer.RedisSerializer;
6import org.springframework.data.redis.serializer.SerializationException;
7public class RedisObjectSerializer implements RedisSerializer<Object> {
8   private Converter<Object, byte[]> serializer = new SerializingConverter();
9   private Converter<byte[], Object> deserializer = new DeserializingConverter();
10  private static final byte[] EMPTY_ARRAY = new byte[0];
11  @Override
12  public byte[] serialize(Object object) throws SerializationException {
13      if (object == null) {
14          return EMPTY_ARRAY;
15      }
16      try {
17          return serializer.convert(object);
18      } catch (Exception ex) {
19          return EMPTY_ARRAY;
20      }
21  }
22  @Override
23  public Object deserialize(byte[] bytes) throws SerializationException {
24      if (this.isEmpty(bytes)) {
25          return null;
26      }
27      try {
28          return deserializer.convert(bytes);
29      } catch (Exception ex) {
30          throw new SerializationException("序列化对象出错!", ex);
31      }
32  }
33  private boolean isEmpty(byte[] data) {
34      return (data == null || data.length == 0);
35  }
36}
37
38

1.5、Thymeleaf整合Shiro标签

在使用JSP的时候可以直接在JSP页面之中使用shiro标签来判断用户是否登录或者来进行授权检测,但是在SpringBoot里面所使用的页面技术为thymeleaf,那么如果要想在这样的模板页面之中实现Shiro控制,就必须去引入新的依赖包,同时做出一些新的配置。

1、【microboot-shiro-web】修改pom.xml配置文件,追加thymeleaf与shiro的整合依赖:


1
2
3
4
5
6
7
1<dependency>
2           <groupId>com.github.theborakompanioni</groupId>
3           <artifactId>thymeleaf-extras-shiro</artifactId>
4           <version>2.0.0</version>
5       </dependency>
6
7

2、【microboot-shiro-web】随后需要修改一下Shiro配置类,在这个配置类之中需要启用Shiro页面支持:


1
2
3
4
5
6
1   @Bean
2   public ShiroDialect getShiroDialect(){ //必须配置此操作才可以使用thymeleaf-extras-shiro开发包
3       return new ShiroDialect();
4   }
5
6

3、【microboot-shiro-web】建立一个新的页面:member_show.html页面:

  • 修改DeptController程序类进行一个跳转的配置:


1
2
3
4
5
6
1   @RequestMapping("/pages/back/dept/show")
2   public String show(){
3       return "dept_show";
4   }
5
6
  • 建立dept_show.html页面,而后在页面之中需要编写以下代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1<!DOCTYPE HTML>
2<html xmlns:th="http://www.thymeleaf.org"
3     xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
4<head>
5   <title>SpringBoot模版渲染</title>
6   <script type="text/javascript" th:src="@{/js/main.js}"></script>
7   <link rel="icon" type="image/x-icon" href="/images/mldn.ico"/>
8   <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
9</head>
10<body>
11  <h1>显示部门信息的内容</h1>
12    <h2>欢迎:<shiro:principal/></h2>
13</body>
14</html>
15
16

4、【microboot-shiro-web】修改dept_show.html页面进行认证和授权检测


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1<!DOCTYPE HTML>
2<html xmlns:th="http://www.thymeleaf.org"
3     xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
4<head>
5   <title>SpringBoot模版渲染</title>
6   <script type="text/javascript" th:src="@{/js/main.js}"></script>
7   <link rel="icon" type="image/x-icon" href="/images/mldn.ico"/>
8   <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
9</head>
10<body>
11  <h1>显示部门信息的内容</h1>
12    <h2>欢迎:<shiro:principal/></h2>
13    <p><a shiro:hasRole="emp">雇员管理</a></p>
14  <p><a shiro:hasRole="dept">部门管理</a></p>
15    <p><a shiro:hasPermission="emp:add">雇员增加</a></p>
16  <p><a shiro:hasPermission="dept:edit">部门修改</a></p>
17    <p shiro:notAuthenticated="">您还未登录,请先登录!</p>
18  <p shiro:authenticated="">欢迎光临!</p>
19</body>
20</html>
21
22

如果在以后进行Shiro与SpringBoot整合的时候一定要考虑使用如上的标签进行整体处理。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

Netty 实现 WebSocket 聊天功能

2022-1-11 12:36:11

安全经验

关于 WordPress WPDB SQL 注入漏洞的安全公告

2017-11-4 11:12:22

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索