Shiro+Jwt+Redis实现登录权限管理系统

前文

配置

POM

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
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--Redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

application.properties

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
# 配置网络
server.cookie-domain=localhost
server.cookie-path=/xxx
server.port=8443
server.port.http=8080
server.ssl.key-store=server.keystore
server.ssl.key-alias=tomcat
server.ssl.enabled=true
server.ssl.key-store-password=123456
server.ssl.key-store-type=JKS

# token有效时间,单位分钟 24*60=1440
token.tokenexpiretime=1440
# 更新令牌时间 2*60=120
token.refreshchecktime=120
# shiro缓存有效期,单位分钟,2*60=120
token.shirocacheexpiretime=120
# token加密密钥
token.secretkey=wxvideoquxue123

# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000

Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SpringBootApplication
@ServletComponentScan
@Component
@EnableCaching //开启声明式缓存
@EnableConfigurationProperties({JWTProperties.class})
public class WxvideoserverApplication extends SpringBootServletInitializer {
/**
* 实现SpringBootServletInitializer可以让spring-boot项目在web容器中运行
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
builder.sources(this.getClass());
return super.configure(builder);
}
public static void main(String[] args) {
SpringApplication.run(WxvideoserverApplication.class, args);
}

}

常量和工具类

基本常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Constants {
/**
* 过期时间
*/
public static class ExpireTime {
private ExpireTime() {
}
public static final int TEN_SEC = 10;//10s
public static final int THIRTY_SEC = 30;//30s
public static final int ONE_MINUTE = 60;//一分钟
public static final int ONE_HOUR = 60 * 60;//一小时
public static final int TWELVE_HOUR = 60 * 60 * 12;//十二小时,单位s
public static final int ONE_DAY = 60 * 60 * 24;//二十四小时
}
}

安全相关name

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
public enum SecurityConsts {
/**
* 加盐
*/
LOGIN_SALT("storyweb-bp"),
/**
* request请求头属性
*/
REQUEST_AUTH_HEADER("Authorization"),

/**
* JWT-userId
*/
USERID("userId"),

/**
* 组织ID
*/
ORG_ID_TOKEN("orgIdToken"),
/**
* 业务线ID
*/
BL_ID_TOKEN("blIdToken"),

/**
* Shiro redis 前缀
*/
PREFIX_SHIRO_CACHE("storyweb-bp:cache:"),

/**
* redis-key-前缀-shiro:refresh_token
*/
PREFIX_SHIRO_REFRESH_TOKEN("storyweb-bp:refresh_token:"),

/**
* redis-key-前缀-shiro:logout
*/
PREFIX_SHIRO_LOGOUT_TOKEN("storyweb-bp:logout:"),

/**
* JWT-currentTimeMillis
*/
CURRENT_TIME_MILLIS("currentTimeMillis");

private String value;

SecurityConsts(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}

UnauthorizedException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UnauthorizedException extends RuntimeException {
private Integer code;
private String errMsg;

public UnauthorizedException(CodeEnums codeEnums) {
super(codeEnums.getMsg());
this.code = codeEnums.getCode();
this.errMsg = codeEnums.getMsg();
}

public UnauthorizedException(String errMsg) {
super(errMsg);
this.errMsg = errMsg;
}
}

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
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
public class ShiroKit {

private static final String NAMES_DELIMETER = ",";

/**
* 加盐参数
*/
public final static String hashAlgorithmName = "MD5";

/**
* 循环次数
*/
public final static int hashIterations = 1024;

/**
* shiro密码加密工具类
*
* @param credentials 密码
* @param saltSource 密码盐
* @return
*/
public static String md5(String credentials, String saltSource) {
ByteSource salt = new Md5Hash(saltSource);
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations).toString();
}


/**
* 获取随机盐值
*
* @param length
* @return
*/
public static String getRandomSalt(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}

/**
* 验证密码是否一致
*
* @param password
* @param salt
* @param md5cipherText
* @return
*/
public static boolean checkMd5Password(String password, String salt, String md5cipherText) {
ByteSource credentialsSalt = new Md5Hash(salt);
//通用散列加密方法
SimpleHash hash = new SimpleHash(ShiroKit.hashAlgorithmName, password, credentialsSalt, ShiroKit.hashIterations);
return md5cipherText.equals(hash.toHex());
}

/**
* 获取当前 Subject
*
* @return Subject
*/
public static Subject getSubject() {
return SecurityUtils.getSubject();
}

/**
* 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用
*
* @param roleName 角色名
* @return 属于该角色:true,否则false
*/
public static boolean hasRole(String roleName) {
return getSubject() != null && roleName != null
&& roleName.length() > 0 && getSubject().hasRole(roleName);
}

/**
* 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
*
* @param roleName 角色名
* @return 不属于该角色:true,否则false
*/
public static boolean lacksRole(String roleName) {
return !hasRole(roleName);
}

/**
* 验证当前用户是否属于以下任意一个角色。
*
* @param roleNames 角色列表
* @return 属于:true,否则false
*/
public static boolean hasAnyRoles(String roleNames) {
boolean hasAnyRole = false;
Subject subject = getSubject();
if (subject != null && roleNames != null && roleNames.length() > 0) {
for (String role : roleNames.split(NAMES_DELIMETER)) {
if (subject.hasRole(role.trim())) {
hasAnyRole = true;
break;
}
}
}
return hasAnyRole;
}

/**
* 验证当前用户是否属于以下所有角色。
*
* @param roleNames 角色列表
* @return 属于:true,否则false
*/
public static boolean hasAllRoles(String roleNames) {
boolean hasAllRole = true;
Subject subject = getSubject();
if (subject != null && roleNames != null && roleNames.length() > 0) {
for (String role : roleNames.split(NAMES_DELIMETER)) {
if (!subject.hasRole(role.trim())) {
hasAllRole = false;
break;
}
}
}
return hasAllRole;
}

/**
* 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
*
* @param permission 权限名
* @return 拥有权限:true,否则false
*/
public static boolean hasPermission(String permission) {
return getSubject() != null && permission != null
&& permission.length() > 0
&& getSubject().isPermitted(permission);
}

/**
* 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。
*
* @param permission 权限名
* @return 拥有权限:true,否则false
*/
public static boolean lacksPermission(String permission) {
return !hasPermission(permission);
}

/**
* 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。与notAuthenticated搭配使用
*
* @return 通过身份验证:true,否则false
*/
public static boolean isAuthenticated() {
return getSubject() != null && getSubject().isAuthenticated();
}

/**
* 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。。
*
* @return 没有通过身份验证:true,否则false
*/
public static boolean notAuthenticated() {
return !isAuthenticated();
}

/**
* 认证通过或已记住的用户。与guset搭配使用。
*
* @return 用户:true,否则 false
*/
public static boolean isUser() {
return getSubject() != null && getSubject().getPrincipal() != null;
}

/**
* 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。用user搭配使用
*
* @return 访客:true,否则false
*/
public static boolean isGuest() {
return !isUser();
}

/**
* 输出当前用户信息,通常为登录帐号信息。
*
* @return 当前用户信息
*/
public static String principal() {
if (getSubject() != null) {
Object principal = getSubject().getPrincipal();
return principal.toString();
}
return "";
}
}

JWT工具类

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
@Component
public class JWTUtil {
@Autowired
private JWTProperties jwtProperties;

@Autowired
private static JWTUtil jwtUtil;

@PostConstruct
public void init() {
jwtUtil = this;
jwtUtil.jwtProperties = this.jwtProperties;
}

/**
* 校验token是否正确
*
* @param token 密钥
* @return 是否正确
*/
public static boolean verify(String token) throws UnsupportedEncodingException {
String secret = getClaim(token, SecurityConsts.USERID.getValue()) + jwtUtil.jwtProperties.getSecretKey();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build();
verifier.verify(token);
return true;
}

/**
* 获得Token中的信息无需secret解密也能获得
*
* @param token
* @param claim
* @return
*/
public static String getClaim(String token, String claim) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(claim).asString();
} catch (JWTDecodeException e) {
return null;
}
}

/**
* 生成签名,5min后过期
*
* @param userId 用户ID
* @param currentTimeMillis 时间
* @return 加密的token
*/
public static String sign(String userId, String currentTimeMillis) throws UnsupportedEncodingException {
// 帐号加JWT私钥加密
String secret = userId + jwtUtil.jwtProperties.getSecretKey();
// 此处过期时间,单位:毫秒
Date date = new Date(System.currentTimeMillis() + jwtUtil.jwtProperties.getTokenExpireTime() * 60 * 1000L);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim(SecurityConsts.USERID.getValue(), userId)
.withClaim(SecurityConsts.CURRENT_TIME_MILLIS.getValue(), currentTimeMillis)
.withExpiresAt(date)
.sign(algorithm);
}
}

Jedis工具类

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
@Component
public class JedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Resource(name = "redisTemplate")
private ValueOperations<String, String> valOpsStr;
@Resource(name = "redisTemplate")
private SetOperations<String, String> valOpsSet;
@Resource(name = "redisTemplate")
private ZSetOperations<String, String> valOpsZSet;
@Resource(name = "redisTemplate")
ListOperations<String, String> valOpsList;
@Resource(name = "redisTemplate")
private HashOperations<String, String, Object> valOpsHash;
private static final ObjectMapper mapper = new ObjectMapper();

/**
* 将数据存入缓存
*
* @param key
* @param val
* @return
*/
public void saveString(String key, String val) {
valOpsStr.set(key, val);
}

/**
* 将数据存入缓存的集合中
*
* @param key
* @param val
* @return
*/
public void saveToSet(String key, String val) {
valOpsSet.add(key, val);
}

/**
* 从Set中获取数据
*
* @param key
* @return keyValue
*/
public String getFromSet(String key) {
return valOpsSet.pop(key);
}

/**
* 将 key的值保存为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是『SET
* if Not eXists』(如果不存在,则 SET)的简写。 <br>
* 保存成功,返回 true <br>
* 保存失败,返回 false
*/
public boolean saveNX(String key, String val) {
return valOpsStr.setIfAbsent(key, val);
}

/**
* 将 key的值保存为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是『SET
* if Not eXists』(如果不存在,则 SET)的简写。 <br>
* 保存成功,返回 true <br>
* 保存失败,返回 false
*
* @param key
* @param val
* @param expire 单位:秒
* 超时时间
* @return 保存成功,返回 true 否则返回 false
*/
public boolean saveNX(String key, String val, int expire) {
boolean ret = saveNX(key, val);
if (ret) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return ret;
}

/**
* 将数据存入缓存(并设置失效时间)
*
* @param key
* @param val
* @param seconds
* @return
*/
public void saveString(String key, String val, int seconds) {
valOpsStr.set(key, val, seconds, TimeUnit.SECONDS);
}

/**
* 将自增变量存入缓存
*/
public void saveSeq(String key, long seqNo) {
redisTemplate.delete(key);
valOpsStr.increment(key, seqNo);
}

/**
* 将递增浮点数存入缓存
*/
public void saveFloat(String key, float data) {
redisTemplate.delete(key);
valOpsStr.increment(key, data);
}

/**
* 保存复杂类型数据到缓存
*
* @param key
* @param obj
* @return
* @throws JsonProcessingException
*/
public void saveObject(String key, Object obj) throws JsonProcessingException {
valOpsStr.set(key, mapper.writeValueAsString(obj));
}

/**
* 保存复杂类型数据到缓存(并设置失效时间)
*
* @param key
* @param obj
* @param seconds
* @return
* @throws JsonProcessingException
*/
public void saveObject(String key, Object obj, int seconds) throws JsonProcessingException {
valOpsStr.set(key, mapper.writeValueAsString(obj), seconds, TimeUnit.SECONDS);
}

/**
* 功能: 存到指定的队列中,不限制队列大小
* 左进右出
*
* @param key
* @param val
*/
public void saveToQueue(String key, String val) {
saveToQueue(key, val, 0);
}

/**
* 功能: 存到指定的队列中
* 左进右出
*
* @param key
* @param val
* @param size 队列大小限制 0:不限制
*/
public void saveToQueue(String key, String val, long size) {
if (size > 0 && valOpsList.size(key) >= size) {
valOpsList.rightPop(key);
}
valOpsList.leftPush(key, val);
}

/**
* 保存到hash集合中
*
* @param hName 集合名
* @param key
* @param val
*/
public void hashSet(String hName, String key, String value) {
valOpsHash.put(hName, key, value);
}

/**
* 保存到hash集合中
*
* @param hName 集合名
* @param key
* @param val
*/
public void hashSet(String hName, Map<String, String> hashMap) {
valOpsHash.putAll(hName, hashMap);
}

/**
* 根据key获取所以值
*
* @param key
* @return
*/
public Map<String, Object> hGetAll(String key) {
return valOpsHash.entries(key);
}

/**
* 保存到hash集合中
*
* @param <T>
* @param hName 集合名
* @param key
* @param val
* @throws JsonProcessingException
*/
public <T> void hashSet(String hName, String key, T t) throws JsonProcessingException {
hashSet(hName, key, mapper.writeValueAsString(t));
}

/**
* 保存到hash集合中 只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与
* key 关联。如果字段已存在,该操作无效果。
*
* @param hName 集合名
* @param key
* @param val
*/
public void hsetnx(String hName, String key, String value) {
valOpsHash.putIfAbsent(hName, key, value);
}

/**
* 保存到hash集合中 只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与
* key 关联。如果字段已存在,该操作无效果。
*
* @param <T>
* @param hName 集合名
* @param key
* @param val
* @throws JsonProcessingException
*/
public <T> void hsetnx(String hName, String key, T t) throws JsonProcessingException {
hsetnx(hName, key, mapper.writeValueAsString(t));
}

/**
* 删除Hash中的key项
*
* @param hName
* @param key
*/
public void hdel(String hName, String key) {
valOpsHash.delete(hName, key);
}

/**
* 取得复杂类型数据
*
* @param key
* @return
* @throws IOException
* @throws JsonMappingException
* @throws JsonParseException
*/
public Object getObject(String key) throws JsonParseException, JsonMappingException, IOException {
String value = valOpsStr.get(key);
if (value == null) {
return null;
}
return mapper.readValue(value, Object.class);
}

/**
* 取得复杂类型数据
*
* @param key
* @param obj
* @param clazz
* @return
* @throws IOException
* @throws JsonMappingException
* @throws JsonParseException
*/
public <T> T getObject(String key, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {
String value = valOpsStr.get(key);
if (value == null) {
return null;
}
return mapper.readValue(value, clazz);
}

/**
* 从缓存中取得字符串数据
*
* @param key
* @return 数据
*/
public String get(String key) {
return valOpsStr.get(key);
}

/**
* 功能: 从指定队列里取得数据
*
* @param key
* @param size 数据长度
* @return
*/
public List<String> getFromQueue(String key) {
return getFromQueue(key, 0);
}

public List<String> getFromQueue(String key, long size) {
boolean flag = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.exists(key.getBytes());
});
if (flag) {
return new ArrayList<>();
}
if (size > 0) {
return valOpsList.range(key, 0, size - 1);
} else {
return valOpsList.range(key, 0, valOpsList.size(key) - 1);
}
}

/**
* 功能: 从指定队列里取得数据
*
* @param key
* @return
*/
public String popQueue(String key) {
return valOpsList.rightPop(key);

}

/**
* 取得序列值的下一个
*
* @param key
* @return
*/
public Long getSeqNext(String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
return connection.incr(key.getBytes());

});
}

/**
* 取得序列值的下一个,增加 value
*
* @param key
* @param value
* @return
*/
public Long getSeqNext(String key, long value) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
return connection.incrBy(key.getBytes(), value);
});

}

/**
* 将序列值回退一个
*
* @param key
* @return
*/
public void getSeqBack(String key) {
redisTemplate.execute((RedisCallback<Long>) connection -> connection.decr(key.getBytes()));
}

/**
* 增加浮点数的值
*
* @param key
* @return
*/
public Double incrFloat(String key, double incrBy) {
return redisTemplate.execute((RedisCallback<Double>) connection -> {
return connection.incrBy(key.getBytes(), incrBy);
});
}

/**
* 从hash集合里取得
*
* @param hName
* @param key
* @return
*/
public Object hashGet(String hName, String key) {
return valOpsHash.get(hName, key);
}

public <T> T hashGet(String hName, String key, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue((String) hashGet(hName, key), clazz);
}

/**
* 判断是否缓存了数据
*
* @param key 数据KEY
* @return 判断是否缓存了
*/
public boolean exists(String key) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.exists(key.getBytes());
});
}

/**
* 判断hash集合中是否缓存了数据, 有问题
*
* @param hName
* @param key
* @return 判断是否缓存了
*/
public boolean hashExists(String hName, String key) {
return valOpsHash.hasKey(hName, key);
}

/**
* 判断是否缓存在指定的集合中
*
* @param key
* @param val
* @return 判断是否缓存了
*/
public boolean isMember(String key, String val) {
return valOpsSet.isMember(key, val);
}

/**
* 从缓存中删除数据
*
* @param string
* @return
*/
public void delKey(String key) {
redisTemplate.delete(key);
// redisTemplate.execute((RedisCallback<Long>) connection -> connection.del(key.getBytes()));
}

/**
* 设置超时时间
*
* @param key
* @param seconds
*/
public void expire(String key, int seconds) {
redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}

/**
* 列出set中所有成员
*
* @param setName
* @return
*/
public Set<String> listSet(String setName) {
return valOpsSet.members(setName);
}

/**
* 向set中追加一个值
*
* @param setName
* @param value
*/
public void appendSet(String setName, String value) {
valOpsSet.add(setName, value);
}

/**
* 向sorted set中追加一个值
*
* @param key
* @param score
* @param member
*/
public void saveToSortedset(String key, String member, Double score) {
valOpsZSet.add(key, member, score);
}

/**
* 根据成员名取得sorted sort分数
*
* @param key set名
* @param member 成员名
* @return 分数
*/
public Double getMemberScore(String key, String member) {
return valOpsZSet.score(key, member);
}

/**
* 从sorted set删除一个值
*
* @param key
* @param member
*/
public void delFromSortedset(String key, String member) {
valOpsZSet.remove(key, member);
}

/**
* 逆序列出sorted set包括分数的set列表
*
* @param key set名
* @param start 开始位置
* @param end 结束位置
* @return 列表
*/
public Set<TypedTuple<String>> listSortedsetRev(String key, int start, int end) {
return valOpsZSet.reverseRangeWithScores(key, start, end);
}

/**
* 逆序取得sorted sort排名
*
* @param key set名
* @param member 成员名
* @return 排名
*/
public Long getReverseRank(String key, String member) {
return valOpsZSet.reverseRank(key, member);
}

/**
* 从hashmap中删除一个值
*
* @param key
* @param field
*/
public void delFromMap(String key, String field) {
valOpsHash.delete(key, field);
}

/**
* 将所有指定的值插入到存于 key 的列表的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表
*
* @param <T>
* @param key
* @param value
* @return
* @throws JsonProcessingException
*/
public <T> Long lpush(String key, T value) throws JsonProcessingException {
return valOpsList.leftPush(key, mapper.writeValueAsString(value));
}

/**
* 只有当 key 已经存在并且存着一个 list 的时候,在这个 key 下面的 list 的头部插入 value。 与 LPUSH 相反,当
* key 不存在的时候不会进行任何操作
*
* @param key
* @param value
* @return
* @throws JsonProcessingException
*/
public <T> Long lpushx(String key, T value) throws JsonProcessingException {
return valOpsList.leftPushIfPresent(key, mapper.writeValueAsString(value));
}

/**
* 返回存储在 key 里的list的长度。 如果 key 不存在,那么就被看作是空list,并且返回长度为 0
*
* @param key
* @return
*/
public Long llen(String key) {
return valOpsList.size(key);
}

/**
* 返回存储在 key 的列表里指定范围内的元素。 start 和 end
* 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推
*
* @param key
* @return
*/
public List<String> lrange(String key, long start, long end) {
return valOpsList.range(key, start, end);
}

/**
* 移除并且返回 key 对应的 list 的第一个元素
*
* @param key
* @return
*/
public String lpop(String key) {
return valOpsList.leftPop(key);
}
}

Cookie工具类

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
public class CookiesUtil {

private final static Logger logger = LoggerFactory.getLogger(CookiesUtil.class);

/**
* 根据名字获取cookie
*
* @param request
* @param name cookie名字
* @return
*/
public static Cookie getCookieByName(HttpServletRequest request, String name) {
Map<String, Cookie> cookieMap = ReadCookieMap(request);
if (cookieMap.containsKey(name)) {
Cookie cookie = (Cookie) cookieMap.get(name);
return cookie;
} else {
return null;
}
}
public static String getValueByName(HttpServletRequest request, String name) {
Map<String, Cookie> cookieMap = ReadCookieMap(request);
if (cookieMap.containsKey(name)) {
Cookie cookie = (Cookie) cookieMap.get(name);
return cookie.getValue();
} else {
return null;
}
}

/**
* 将cookie封装到Map里面
*
* @param request
* @return
*/
private static Map<String, Cookie> ReadCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
Cookie[] cookies = request.getCookies();
if (null != cookies) {
for (Cookie cookie : cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
}

/**
* 保存Cookies
*
* @param response servlet请求
* @param value 保存值
* @author jxf
*/
public static HttpServletResponse setCookie(String domain,String path,HttpServletResponse response, String name, String value, int time) {
// new一个Cookie对象,键值对为参数
Cookie cookie = new Cookie(name, value);
// tomcat下多应用共享
cookie.setPath(path);
// 如果cookie的值中含有中文时,需要对cookie进行编码,不然会产生乱码
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setDomain(domain);
try {
URLEncoder.encode(value, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.error("转码失败", e);
}
cookie.setMaxAge(time);

//手动覆盖添加SameSite-Strict
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
}
// 将Cookie添加到Response中,使之生效
// addCookie后,如果已经存在相同名字的cookie,则最新的覆盖旧的cookie
response.addCookie(cookie);
return response;
}
}

配置类

跨域访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
corsConfiguration.addAllowedHeader("*"); // 2允许任何头
corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等)
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4
return new CorsFilter(source);
}
}

Https配置

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
@Configuration
public class HttpsConfig {

@Value("${server.port.http}")
private int serverPortHttp;

@Value("${server.port}")
private int serverPortHttps;

@Bean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/*");
securityConstraint.addCollection(securityCollection);
context.addConstraint(securityConstraint);
}
};
factory.addAdditionalTomcatConnectors(redirectConnector());
return factory;
}

private Connector redirectConnector() {
Connector connector = new Connector(Http11NioProtocol.class.getName());
connector.setScheme("http");
connector.setPort(serverPortHttp);
connector.setSecure(false);
connector.setRedirectPort(serverPortHttps);
return connector;
}
}

RedisCache配置

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
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {

@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private int port;

@Value("${spring.redis.timeout}")
private int timeout;

@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;

@Value("${spring.redis.jedis.pool.max-wait}")
private long maxWaitMillis;

@Value("${spring.redis.password}")
private String password;

@Autowired
RedisConnectionFactory connectionFactory;

@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);

// key采用String的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}

@Bean(name = "cacheRedisTemplate")
public RedisTemplate<String, String> cacheRedisTemplate() {
RedisTemplate<String, String> template = new RedisTemplate<String, String>();
// 设置redis连接Factory
template.setConnectionFactory(connectionFactory);
// Redis value 序列化
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
// Redis key 序列化
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}

RestTemplate配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}

@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//读取超时时间为单位为60秒
factory.setReadTimeout(1000 * 60);
//连接超时时间设置为10秒
factory.setConnectTimeout(1000 * 10);
return factory;
}
}

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
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
@Configuration
public class ShiroConfig {
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
creator.setProxyTargetClass(true);
return creator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager manager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(manager);
return advisor;
}

//配置核心安全事务管理器
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager(AuthRealm authRealm, ShiroCacheManager shiroCacheManager) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authRealm);
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);

manager.setCacheManager(shiroCacheManager);
return manager;
}

//注意不要加@Bean注解,不然spring会自动注册成filter
protected JWTFilter createJWTFilter(JWTProperties jwtProp, ISyncCacheService syncCacheService,JedisUtils jedisUtils) {
return new JWTFilter(jwtProp, syncCacheService,jedisUtils);
}

protected SystemLogoutFilter createSystemLogoutFilter(JWTProperties jwtProp,JedisUtils jedisUtils) {
return new SystemLogoutFilter(jedisUtils,jwtProp);
}

@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager,
JWTProperties jwtProp,
ISyncCacheService syncCacheService,JedisUtils jedisUtils) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 添加jwt过滤器
Map<String, Filter> filterMap = bean.getFilters();
filterMap.put("jwt", createJWTFilter(jwtProp, syncCacheService,jedisUtils));
filterMap.put("logout", createSystemLogoutFilter(jwtProp,jedisUtils));
bean.setFilters(filterMap);
//配置登录的url和登录成功的url
bean.setLoginUrl("/login/loginWeb");
bean.setSuccessUrl("/");
bean.setUnauthorizedUrl("/401");

//配置访问权限拦截器
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//anon表示可以匿名访问,authc表示需要认证才可以访问
//表示需要认证才可以访问
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/login/wxRegister", "jwt");
filterChainDefinitionMap.put("/login/sendVerificationCode", "jwt");
filterChainDefinitionMap.put("/user/createNewUser", "jwt");
//login不做认证,noSessionCreation的作用是用户在操作session时会抛异常
filterChainDefinitionMap.put("/login/loginWeb", "noSessionCreation,anon");
filterChainDefinitionMap.put("/login/loginWx", "noSessionCreation,anon");
//做用户认证,permissive参数的作用是当token无效时也允许请求访问,不会返回鉴权未通过的错误
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/layui/*", "anon");
filterChainDefinitionMap.put("/layui/**", "anon");
filterChainDefinitionMap.put("/layui/*.*", "anon");
filterChainDefinitionMap.put("/js/common/jquery.js", "anon");
filterChainDefinitionMap.put("/js/login.js", "anon");
filterChainDefinitionMap.put("/css/common.css", "anon");
filterChainDefinitionMap.put("/css/login.css", "anon");
filterChainDefinitionMap.put("/*", "jwt");
filterChainDefinitionMap.put("/**", "jwt");
filterChainDefinitionMap.put("/*.*", "jwt");
filterChainDefinitionMap.put("/401", "anon");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
}

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* 域,Shiro从从Realm获取安全数据(如用户、角色、权限)
* 就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;
* 也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;
* 可以把Realm看成DataSource,即安全数据源
*/
@Service
public class AuthRealm extends AuthorizingRealm {
private Logger logger = Logger.getLogger(AuthRealm.class);
private UserService userService;
private RoleService roleService;
private AccessService accessService;
private JedisUtils jedisUtils;

@Autowired
public AuthRealm(UserService userService, RoleService roleService, AccessService accessService, JedisUtils jedisUtils) {
this.userService = userService;
this.roleService = roleService;
this.accessService = accessService;
this.jedisUtils = jedisUtils;
}


/**
* 返回一个唯一的Realm名字
*/
@Override
public String getName() {
return super.getName();
}

/**
* 判断此Realm是否支持此Token
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}

/**
* 根据Token获取认证信息,默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
logger.info("doGetAuthenticationInfo()获取认证信息进行校验");
String token = (String) auth.getCredentials();
// 解密获得userId,用于和数据库进行对比
String userId = JWTUtil.getClaim(token, SecurityConsts.USERID.getValue());
if (userId == null) {
throw new AuthenticationException("token invalid");
}
//用户信息是否存在
User user = userService.findByUserId(userId);
if (user == null) {
throw new AuthenticationException("User didn't existed!");
}
try {
//Token检验
if (!JWTUtil.verify(token)) {
throw new AuthenticationException("Username or password error");
}
} catch (UnsupportedEncodingException ex) {
throw new AuthenticationException("Username or password error");
}
return new SimpleAuthenticationInfo(token, token, "my_realm");
}

/**
* 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("doGetAuthorizationInfo()获取角色权限等信息进行校验");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//获取用户信息
String userId = JWTUtil.getClaim(principals.toString(), SecurityConsts.USERID.getValue());
User user = userService.findByUserId(userId);
//获取用户角色信息
List<UserRole> roleList = userService.findAllUserRoleByUserId(user.getUserId());
for (UserRole role : roleList) {
authorizationInfo.addRole(role.getRole().getRoleName());
//获取角色权限信息
List<RoleAccess> accessList = roleService.findAllRoleAccessByRoleId(role.getRole().getRoleId());
for (RoleAccess access : accessList) {
authorizationInfo.addStringPermission(access.getAccess().toString());
}
}
return authorizationInfo;
}
}

Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JWTToken implements AuthenticationToken {
/**
* 密钥
*/
private String token;

public JWTToken(String token) {
this.token = token;
}

@Override
public Object getPrincipal() {
return token;
}

@Override
public Object getCredentials() {
return token;
}
}

JWTProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ConfigurationProperties(prefix = "token")
@Getter
@Setter
@NoArgsConstructor
@Data
public class JWTProperties {
/**
* token过期时间,单位分钟
*/
Integer tokenExpireTime;
/**
* 更新令牌时间,单位分钟
*/
Integer refreshCheckTime;
/**
* Shiro缓存有效期,单位分钟
*/
Integer shiroCacheExpireTime;
/**
* token加密密钥
*/
String secretKey;
}

JWTFilter

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
public class JWTFilter extends BasicHttpAuthenticationFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//ShiroConfig声明的LifecycleBeanPostProcessor会因为鸡蛋问题导致JWTProperties注入null
private JWTProperties jwtProperties;
private ISyncCacheService syncCacheService;
private JedisUtils jedisUtils;

@Value("${server.cookie-domain}")
private String adress;
@Value("${server.cookie-path}")
private String path;

public JWTFilter() {
}

public JWTFilter(JWTProperties jwtProperties, ISyncCacheService syncCacheService,JedisUtils jedisUtils) {
this.jwtProperties = jwtProperties;
this.syncCacheService = syncCacheService;
this.jedisUtils = jedisUtils;
}

/**
* 对跨域提供支持,流程顺序:preHandle->isAccessAllowed->isLoginAttempt->executeLogin
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}

/**
* 表示是否允许访问
* 这里我们详细说明下为什么最终返回的都是true,即允许访问
* 例如我们提供一个地址 GET /article
* 登入用户和游客看到的内容是不同的
* 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西
* 所以我们在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入
* 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可
* 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
if (isLoginAttempt(request, response)) {
//无登陆信息,进行登陆验证
logger.info("无登陆信息,进行登陆验证->executeLogin");
return executeLogin(request, response);
}else {
//检查已有登陆信息
logger.info("已有登陆信息,数据校验");
String authorization = CookiesUtil.getCookieByName((HttpServletRequest)request,SecurityConsts.REQUEST_AUTH_HEADER.getValue()).getValue();
if(!JWTUtil.verify(authorization)){
logger.info("登陆信息未通过校验,请重新登陆");
throw new UnauthorizedException("登陆信息未通过校验,请重新登陆");
}else {
if(!getSubject(request, response).isAuthenticated()){
logger.info("登陆信息已失效,请重新登陆");
throw new UnauthorizedException("登陆信息已失效,请重新登陆");
}else {
//获取账号
String userId = JWTUtil.getClaim(authorization, SecurityConsts.USERID.getValue());
//判断是否在登出状态
String tokenKey = SecurityConsts.PREFIX_SHIRO_LOGOUT_TOKEN.getValue() + userId;
if(jedisUtils.exists(tokenKey)){
logger.info("登陆验证:此账户已登出,请重新登陆");
throw new UnauthorizedException("此账户已登出,请重新登陆");
}else {
return true;
}
}
}
}
} catch (Exception e) {
logger.info(e.getMessage());
try {
WebUtils.issueRedirect(request,response,"/");
}catch (IOException ex){
logger.info(ex.getMessage());
return false;
}
return false;
}
}

/**
* 判断用户是否想要登入
* 只有用户携带令牌时才考虑进行登陆验证
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
Cookie cookie = CookiesUtil.getCookieByName(req,SecurityConsts.REQUEST_AUTH_HEADER.getValue());
return cookie != null;
}

/**
* 登陆验证
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//从Cookie获取token
Cookie cookie = CookiesUtil.getCookieByName(httpServletRequest,SecurityConsts.REQUEST_AUTH_HEADER.getValue());
String authorization;
if(cookie == null){
logger.info("无登陆数据");
authorization = null;
}else {
authorization = cookie.getValue();
logger.info("已有登陆数据:" + authorization);
}
//获取账号
String userId = JWTUtil.getClaim(authorization, SecurityConsts.USERID.getValue());
//判断是否仍在登出状态
String tokenKey = SecurityConsts.PREFIX_SHIRO_LOGOUT_TOKEN.getValue() + userId;
if(jedisUtils.exists(tokenKey)){
logger.info("登陆验证:登录后清除登出缓存信息");
jedisUtils.delKey(tokenKey);
}else {
JWTToken token = new JWTToken(authorization);
//提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(token);
//检查是否需要更换token,需要则重新颁发
this.refreshTokenIfNeed(userId, authorization, response);
}
// 如果没有抛出异常则代表登入成功,返回true
return true;
}

/**
* 检查是否需要,刷新Token
*
* @param userId
* @param authorization
* @param response
* @return
*/
private void refreshTokenIfNeed(String userId, String authorization, ServletResponse response) throws UnsupportedEncodingException {
Long currentTimeMillis = System.currentTimeMillis();
//检查刷新规则
if (this.refreshCheck(authorization, currentTimeMillis)) {
String lockName = SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN.getValue() + userId;
boolean b = syncCacheService.getLock(lockName, Constants.ExpireTime.ONE_HOUR);
if (b) {
logger.info(String.format("为账户%s颁发新的令牌", userId));
String newToken = JWTUtil.sign(userId, String.valueOf(currentTimeMillis));
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
CookiesUtil.setCookie(adress,path,httpServletResponse,SecurityConsts.REQUEST_AUTH_HEADER.getValue(),newToken,3600);
}
syncCacheService.releaseLock(lockName);
}
}

/**
* 检查是否需要更新Token
*
* @param authorization
* @param currentTimeMillis
* @return
*/
private boolean refreshCheck(String authorization, Long currentTimeMillis) {
String tokenMillis = JWTUtil.getClaim(authorization, SecurityConsts.CURRENT_TIME_MILLIS.getValue());
if (tokenMillis != null && currentTimeMillis - Long.parseLong(tokenMillis) > (jwtProperties.getRefreshCheckTime() * 60 * 1000L)) {
return true;
}
return false;
}


/**
* 将非法请求跳转到 /401
*/
private void response401(ServletRequest req, ServletResponse resp, String msg) {
try {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.sendRedirect("/401");
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}

SystemLogoutFilter

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
public class SystemLogoutFilter extends LogoutFilter {
private static final Logger logger = LoggerFactory.getLogger(SystemLogoutFilter.class);
private JedisUtils jedisUtils;
private JWTProperties jwtProperties;

public SystemLogoutFilter() {
}

public SystemLogoutFilter(JedisUtils jedisUtils, JWTProperties jwtProperties) {
this.jedisUtils = jedisUtils;
this.jwtProperties = jwtProperties;
}

//当服务器收到一个可预知的请求时,首先在redis中查询是否存在当前JWT
// 如果存在返回false,否则在redis中插入当前jwt,这条数据的失效时间为当前jwt的剩余有效时间。
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response){
Subject subject = getSubject(request, response);
subject.logout();
//添加到Redis
String userId = JWTUtil.getClaim(CookiesUtil.getValueByName((HttpServletRequest)request,SecurityConsts.REQUEST_AUTH_HEADER.getValue()), SecurityConsts.USERID.getValue());
if(userId != null){
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
//新增登出缓存,并清除刷新缓存
String tokenKey = SecurityConsts.PREFIX_SHIRO_LOGOUT_TOKEN.getValue() + userId;
if (!jedisUtils.exists(tokenKey)) {
logger.info("登出:新增登出标记到缓存");
jedisUtils.saveString(tokenKey, currentTimeMillis, jwtProperties.getRefreshCheckTime() * 60 * 60);
//清除RefreshToken缓存的时间戳
String refreshTokenKey = SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN.getValue() + userId;
if (jedisUtils.exists(refreshTokenKey)) {
logger.info("登出:清除RefreshToken缓存的时间戳");
jedisUtils.delKey(refreshTokenKey);
}
// 清除可能存在的Shiro权限信息缓存
String cacheKey = SecurityConsts.PREFIX_SHIRO_CACHE.getValue() + userId;
if (jedisUtils.exists(cacheKey)) {
jedisUtils.delKey(cacheKey);
}
}
}
//不执行后续的过滤器
return false;
}
}

ShiroCache

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
public class ShiroCache<K, V> implements Cache<K, V> {
private JedisUtils jedisUtils;
private JWTProperties jwtProperties;

public ShiroCache(JedisUtils jedisUtils, JWTProperties jwtProperties) {
this.jedisUtils = jedisUtils;
this.jwtProperties = jwtProperties;
}

/**
* 获取缓存
*
* @param key
* @return
* @throws CacheException
*/
@Override
public Object get(Object key) throws CacheException {
String tempKey = this.getKey(key);
Object result = null;
if (jedisUtils.exists(tempKey)) {
try {
result = jedisUtils.getObject(tempKey, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}

/**
* 保存缓存
*
* @param key
* @param value
* @return
* @throws CacheException
*/
@Override
public Object put(Object key, Object value) throws CacheException {
String tempLey = this.getKey(key);
try {
jedisUtils.saveObject(tempLey, value);
return value;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}

/**
* 移除缓存
*
* @param key
* @return
* @throws CacheException
*/
@Override
public Object remove(Object key) throws CacheException {
String tempKey = this.getKey(key);
if (jedisUtils.exists(tempKey)) {
jedisUtils.delKey(tempKey);
}
return null;
}

@Override
public void clear() throws CacheException {

}

@Override
public int size() {
return 20;
}

@Override
public Set<K> keys() {
return null;
}

@Override
public Collection<V> values() {
Set keys = this.keys();
List<V> values = new ArrayList<>();
try {
for (Object key : keys) {
values.add((V) jedisUtils.getObject(this.getKey(key)));
}
} catch (IOException e) {
e.printStackTrace();
}
return values;
}

/**
* 缓存的key名称获取为shiro:cache:account
*
* @param key
* @return
*/
private String getKey(Object key) {
return SecurityConsts.PREFIX_SHIRO_CACHE + JWTUtil.getClaim(key.toString(), SecurityConsts.USERID.getValue());
}
}

ShiroCacheManager

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class ShiroCacheManager implements CacheManager {
@Autowired
JedisUtils jedisUtils;
@Autowired
JWTProperties jwtProperties;

@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new ShiroCache<K, V>(jedisUtils, jwtProperties);
}
}

实体类

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "T_XXXX_USER")
public class User {
@Id
private String userId;
private String openid;
private String pwd;
private String userName;
private String nickName;
...
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private java.sql.Timestamp regTime;
private String address;
private String phone;
private String imgUrl;
...
}

Role

1
2
3
4
5
6
7
8
9
10
11
12
13
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "T_XXXX_ROLE")
public class Role {
@Id
@GeneratedValue(generator = "idSequence", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "idSequence", sequenceName = "ROLE", allocationSize = 1)
private Integer roleId;
private String roleName;
private Boolean status;
}

UserRole

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
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "T_XXXX_USER_ROLE")
public class UserRole {
@Id
@GeneratedValue(generator = "idSequence", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "idSequence", sequenceName = "USER_ROLE", allocationSize = 1)
private Integer urId;
private String userId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "roleId")
private Role role;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private java.sql.Timestamp createTime;

public UserRole(String userId, Role role) {
this.userId = userId;
this.role = role;
this.createTime = new Timestamp(System.currentTimeMillis());
}
}

Access

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "T_XXXX_ACCESS")
public class Access {
@Id
@GeneratedValue(generator = "idSequence", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "idSequence", sequenceName = "ACCESS", allocationSize = 1)
private Integer accessId;
private String title;
private String code;
private Boolean status;

}

RoleAccess

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
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "T_XXXX_ROLE_ACCESS")
public class RoleAccess {
@Id
@GeneratedValue(generator = "idSequence", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "idSequence", sequenceName = "ROLE_ACCESS", allocationSize = 1)
private Integer raId;
private Integer roleId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "accessId")
private Access access;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private java.sql.Timestamp createTime;

public RoleAccess(Integer roleId, Access access) {
this.roleId = roleId;
this.access = access;
this.createTime = new Timestamp(System.currentTimeMillis());
}
}
1
2
3
4
5
6
7
8
9
10
11
@Data
@NoArgsConstructor
@Entity
@Table(name = "T_WXMP_MENU")
public class Menu {
@Id
@GeneratedValue(generator = "idSequence", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "idSequence", sequenceName = "MENU", allocationSize = 1)
private Integer menuId;
private String menuName;
}

RoleMenu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@NoArgsConstructor
@Entity
@Table(name = "T_WXMP_ROLE_MENU")
public class RoleMenu {
@Id
@GeneratedValue(generator = "idSequence", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "idSequence", sequenceName = "ROLE_MENU", allocationSize = 1)
private Integer rmId;
private Integer roleId;
private Integer menuId;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private java.sql.Timestamp createTime;
}

持久层

省略…

服务层

UserService

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
public interface UserService {
/**
* 用户登录
*
* @param userId 用户ID
* @param password 密码
* @return BaseVo
*/
BaseVo login(String userId, String password, HttpServletResponse response) throws UnsupportedEncodingException;

/**
* 退出当前用户登陆
* JWT设置失效时间为当前时间
* @return
*/
BaseVo logout(HttpServletRequest request);

/**
* 微信登录
* 流程:小程序->开发服务器->微信接口
* 根据小程序提供的code调用微信code2session接口获取openid和session_key
* 再根据openid和session_key自定义登陆态(Token),返回Token
* 1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
* 2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。
* @return BaseVo
*/
BaseVo loginWx(String code,HttpServletResponse response)throws UnsupportedEncodingException;

/**
* 发送短信验证码
* @param phone 手机号
* @return BaseVo
* @throws ClientException
*/
BaseVo sendVerificationCode(String phone)throws ClientException;

/**
* 微信注册
* 验证用户信息和验证码,并注册用户,成功后登陆用户信息,返回token
* @param wxRegisterVo
* @param response
* @return BaseVo
*/
BaseVo wxRegister(WxRegisterVo wxRegisterVo, HttpServletResponse response)throws ClientException,UnsupportedEncodingException;

/**
* 新增用户
* @param user
* @return
*/
BaseVo createNewUser(User user);

/**
* 修改用户密码
* @param userPassword
* @return
*/
BaseVo changePwd(UserPasswordVo userPassword);

/**
* 修改用户信息
* 性别,昵称等
*
* @param nickName 昵称
* @param address 地址
* @param imgUrl 头像
* @return BaseVo
*/
BaseVo changeUserInfo(String userId,String nickName, String address,String imgUrl);

/**
* 修改用户角色
* @param userRoleVo
* @return
*/
BaseVo updateUserRoles(UserRoleVo userRoleVo);

/**
* 根据ID查询用户信息
* @param userId 用户ID
* @return 用户信息
*/
User findByUserId(String userId);

/**
* 获取用户ID角色
* @param userId
* @return
*/
List<UserRole> findAllUserRoleByUserId(String userId);

/**
* 根据用户ID查询其所有权限
* @param userId
* @return
*/
List<RoleAccess> findAllRoleAccessByUserId(String userId);
}
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
@Service
public class UserServiceImpl implements UserService {
private Logger logger = Logger.getLogger(UserServiceImpl.class);
private JWTProperties jwtProperties;
private JedisUtils jedisUtils;
private RestTemplate restTemplate;
private UserRepository userRepository;
private UserRoleRepository userRoleRepository;
private RoleRepository roleRepository;
private RoleAccessRepository roleAccessRepository;
private StudentRepository studentRepository;

@Value("${wx.applet.appid}")
private String appid;

@Value("${wx.applet.appsecret}")
private String appSecret;

@Value("${alMsg.product}")
private String product;

//dyalMsgapi.aliyuncs.com
@Value("${alMsg.domain}")
private String domain;

@Value("${alMsg.accessKeySecret}")
private String accessKeySecret;

@Value("${alMsg.accessKeyId}")
private String accessKeyId;
@Value("${server.cookie-domain}")
private String adress;
@Value("${server.cookie-path}")
private String path;

//短信API产品名称(短信产品名固定,无需修改)
@Value("${alMsg.endpoint}")
private String endpoint;

@Autowired
public UserServiceImpl(JWTProperties jwtProperties, JedisUtils jedisUtils, RestTemplate restTemplate, UserRepository userRepository, UserRoleRepository userRoleRepository, RoleRepository roleRepository, RoleAccessRepository roleAccessRepository, StudentRepository studentRepository) {
this.jwtProperties = jwtProperties;
this.jedisUtils = jedisUtils;
this.restTemplate = restTemplate;
this.userRepository = userRepository;
this.userRoleRepository = userRoleRepository;
this.roleRepository = roleRepository;
this.roleAccessRepository = roleAccessRepository;
this.studentRepository = studentRepository;
}

@Override
public BaseVo login(String userId, String password, HttpServletResponse response) throws UnsupportedEncodingException {
User user = userRepository.findByUserId(userId);
if (user == null) {
//用户不存在
return new BaseVo(CodeEnums.USER_NOT_EXIST.getCode(), CodeEnums.USER_NOT_EXIST.getMsg());
}else {
//微信账号直接提示账号不存在
if (user.getOpenid() == null || "".equals(user.getOpenid())) {
//WEB登陆
logger.info("WEB登陆");
} else {
//微信登陆
logger.info("微信登陆");
return new BaseVo(1,"当前用户只能登陆微信端");
}
//密码校验
logger.info("持久:" + user.getPwd());
logger.info("输入:" + password);
if (!ShiroKit.checkMd5Password(password, SecurityConsts.LOGIN_SALT.getValue(), user.getPwd())) {
return new BaseVo(CodeEnums.USER_PWD_FAIL.getCode(), CodeEnums.USER_PWD_FAIL.getMsg());
}
//登录成功
this.loginSuccess(user.getUserId(),user, response);
return new BaseVo(CodeEnums.TOKEN_CHECK_SUCCESS.getCode(), CodeEnums.TOKEN_CHECK_SUCCESS.getMsg());
}
}

/**
* 退出当前用户登陆
* 退出则存用户信息到redis,再次访问时判断,若有数据则表示已失效
* @return
*/
@Override
public BaseVo logout(HttpServletRequest request){

return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg());
}

/**
* 微信登录
* 流程:小程序->开发服务器->微信接口
* 根据小程序提供的code调用微信code2session接口获取openid和session_key
* 再根据openid和session_key自定义登陆态(Token),返回Token
* 1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
* 2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。
* @return
*/
@Override
public BaseVo loginWx(String code,HttpServletResponse response)throws UnsupportedEncodingException {
String resultJson = code2Session(code);
Code2SessionResponse sessionResponse = JSON.parseObject(resultJson, Code2SessionResponse.class);
if (!"0".equals(sessionResponse.getErrcode())) {
throw new AuthenticationException("微信验证失败: " + sessionResponse.getErrmsg());
}else {
//从本地数据库中查找用户是否存在
User user = userRepository.findByOpenid(sessionResponse.getOpenid());
if (user == null) {
return new BaseVo(CodeEnums.WX_ACCOUNT_NOT_REGEISTER.getCode(), CodeEnums.WX_ACCOUNT_NOT_REGEISTER.getMsg());
}
this.loginSuccess(user.getUserId(),user, response);
//登录成功
return new BaseVo(CodeEnums.TOKEN_CHECK_SUCCESS.getCode(), CodeEnums.TOKEN_CHECK_SUCCESS.getMsg(),user,1);
}
}

/**
* 微信登录
* 流程:小程序->开发服务器->微信接口
* 根据小程序提供的code调用微信code2session接口获取openid和session_key
* 再根据openid和session_key自定义登陆态(Token),返回Token
* 1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
* 2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。
* @return BaseVo
*/
@Override
public BaseVo sendVerificationCode(String phone) throws ClientException {

//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId,
accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);

//组装请求对象
SendSmsRequest request = new SendSmsRequest();
//使用post提交
request.setMethod(MethodType.POST);
//必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式;发送国际/港澳台消息时,接收号码格式为国际区号+号码,如“85200000000”
request.setPhoneNumbers(phone);
//必填:短信签名-可在短信控制台中找到
request.setSignName("趣学厦门");
//必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
request.setTemplateCode("SMS_168726524");
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
//友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
request.setTemplateParam("{\"code\":\"" + createRandomNum(6) + "\"}");
//可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
// request.setOutId("yourOutId");
//请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
logger.info(sendSmsResponse.getMessage());
if(sendSmsResponse.getCode() != null && "OK".equals(sendSmsResponse.getCode())) {
//请求成功
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg());
}else {
throw new ApiException(1,"短信推送失败");
}
}

/**
* 微信注册
* 成功后登陆用户信息,返回token
* @param wxRegisterVo
* @param response
* @return BaseVo
*/
@Transactional
@Override
public synchronized BaseVo wxRegister(WxRegisterVo wxRegisterVo, HttpServletResponse response) throws ClientException,UnsupportedEncodingException {
if(wxRegisterVo.getVerCode().length() != 6){
return new BaseVo(CodeEnums.MSG_FORMAT_ERR.getCode(), CodeEnums.MSG_FORMAT_ERR.getMsg());
}
//检查验证码是否一致
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
QuerySendDetailsRequest request = new QuerySendDetailsRequest();
//必填-号码
request.setPhoneNumber(wxRegisterVo.getPhone());
//可选-流水号
// request.setBizId("111");
//必填-发送日期 支持30天内记录查询,格式yyyyMMdd
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
request.setSendDate(ft.format(new Date()));
//必填-页大小
request.setPageSize(10L);
//必填-当前页码从1开始计数
request.setCurrentPage(1L);
QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
logger.info(querySendDetailsResponse.getCode());
logger.info(querySendDetailsResponse.getMessage());
boolean checkVerCode = false;
for(QuerySendDetailsResponse.SmsSendDetailDTO dto : querySendDetailsResponse.getSmsSendDetailDTOs()){
logger.info("verCode=" + wxRegisterVo.getVerCode());
logger.info("Content=" + dto.getContent());
logger.info("ErrCode=" + dto.getErrCode());
logger.info("OutId=" + dto.getOutId());
logger.info("PhoneNum=" + dto.getPhoneNum());
logger.info("ReceiveDate=" + dto.getReceiveDate());
logger.info("SendDate=" + dto.getSendDate());
logger.info("SendStatus=" + dto.getSendStatus());
logger.info("Template=" + dto.getTemplateCode());
if(dto.getContent().contains(wxRegisterVo.getVerCode())){
checkVerCode = true;
logger.info("验证码确认成功");
break;
}
}
if(checkVerCode){
//根据userId和userName获取用户信息
Student student = studentRepository.findByStudentIdAndStudentName(wxRegisterVo.getUserId(),wxRegisterVo.getUserName());
if(student == null){
return new BaseVo(1,"未找到所给学生信息,请确认学号姓名是否输入正确");
}
//调用微信code2session接口获取openid和session_key
String resultJson = code2Session(wxRegisterVo.getCode());
Code2SessionResponse sessionResponse = JSON.parseObject(resultJson, Code2SessionResponse.class);
if (!"0".equals(sessionResponse.getErrcode())) {
throw new AuthenticationException("微信验证失败: " + sessionResponse.toString());
}else {
String openid = sessionResponse.getOpenid();
//从本地数据库中查找用户是否存在
User user = userRepository.findByOpenid(openid);
if (user != null) {
return new BaseVo(1,"此微信账号已有绑定用户");
}else {
user = userRepository.findByUserId(wxRegisterVo.getUserId());
if (user != null) {
user.register(openid, wxRegisterVo.getNickName(),wxRegisterVo.getAddress(), wxRegisterVo.getPhone());
}else {
user = new User(wxRegisterVo.getUserId(), openid, wxRegisterVo.getUserName(),
wxRegisterVo.getNickName(),
student.getSchool(), student.getDepartment(),student.getMajor(),
student.getClassName(), student.getGrade(), student.getSex(),
wxRegisterVo.getAddress(), wxRegisterVo.getPhone());
}
user = userRepository.saveAndFlush(user);
}
//登陆
this.loginSuccess(user.getUserId(),user, response);
return new BaseVo(CodeEnums.TOKEN_CHECK_SUCCESS.getCode(), CodeEnums.TOKEN_CHECK_SUCCESS.getMsg(),user,1);
}
}else {
//短信验证失败
return new BaseVo(CodeEnums.MSG_CHECK_ERR.getCode(), CodeEnums.MSG_CHECK_ERR.getMsg());
}
}

@Transactional
@Override
public synchronized BaseVo createNewUser(User user) {
User existUser = userRepository.findByUserId(user.getUserId());
if (existUser != null) {
//账号已存在
return new BaseVo(1, "账号已经存在");
} else {
if (user.getOpenid() != null) {
//微信
return new BaseVo(1, "调用微信端接口异常");
} else {
//保存密码
if (!StringUtils.isEmpty(user.getPwd())) {
user.setPwd(ShiroKit.md5(user.getPwd(), SecurityConsts.LOGIN_SALT.getValue()));
} else {
//检查密码规范
return new BaseVo(2, "密码不符合规范");
}
}
userRepository.saveAndFlush(user);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg());
}
}

/**
* 修改密码
* @param userPassword
* @return
*/
@Transactional
@Override
public synchronized BaseVo changePwd(UserPasswordVo userPassword) {
if (!StringUtils.isEmpty(userPassword.getPassword()) && !StringUtils.isEmpty(userPassword.getNewPassword())) {
User user = userRepository.findById(userPassword.getUserId()).orElseThrow(DBDataNotFoundException::new);
//解码
String encodePassword = ShiroKit.md5(userPassword.getPassword(), SecurityConsts.LOGIN_SALT.getValue());
if (user.getPwd().equals(encodePassword)) {
user.setPwd(ShiroKit.md5(userPassword.getNewPassword(), SecurityConsts.LOGIN_SALT.getValue()));
userRepository.saveAndFlush(user);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg());
} else {
//原始密码错误
return new BaseVo(CodeEnums.USER_OLD_PWD_ERR.getCode(), CodeEnums.USER_OLD_PWD_ERR.getMsg());
}
}
return new BaseVo(CodeEnums.PARAMETERS_MISSING.getCode(), CodeEnums.PARAMETERS_MISSING.getMsg());
}


/**
* 修改用户信息
* 性别,昵称等
* @return BaseVo
*/
@Transactional
@Override
public synchronized BaseVo changeUserInfo(String userId,String nickName, String address,String imgUrl) {
User user = userRepository.findById(userId).orElseThrow(DBDataNotFoundException::new);
user.setNickName(nickName);
user.setAddress(address);
user.setImgUrl(imgUrl);
userRepository.saveAndFlush(user);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg());
}


/**
* 修改用户角色
* @param userRoleVo
* @return
*/
@Transactional
@Override
public synchronized BaseVo updateUserRoles(UserRoleVo userRoleVo){
//清除用户所有角色
userRoleRepository.deleteAllByUserId(userRoleVo.getUserId());
List<UserRole> userRoles = new ArrayList<>();
List<Role> roles = roleRepository.findAllByRoleIdIn(userRoleVo.getRoleIds());
if(roles.size() != userRoleVo.getRoleIds().size()){
return new BaseVo(1,"角色数据异常");
}
for(Role role : roles){
UserRole userRole = new UserRole(userRoleVo.getUserId(),role);
userRoles.add(userRole);
}
userRoleRepository.saveAll(userRoles);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg(),userRoles,userRoles.size());
}

@Override
public User findByUserId(String userId) {
return userRepository.findById(userId).orElseThrow(DBDataNotFoundException::new);
}

/**
* 获取用户ID角色
* @param userId
* @return
*/
@Override
public List<UserRole> findAllUserRoleByUserId(String userId) {
return userRoleRepository.findAllByUserId(userId);
}

/**
* 根据用户ID查询其所有权限
* @param userId
* @return
*/
@Override
public List<RoleAccess> findAllRoleAccessByUserId(String userId){
User user = userRepository.findById(userId).orElseThrow(DBDataNotFoundException::new);
List<UserRole> userRoles = userRoleRepository.findAllByUserId(userId);
List<Integer> roleIds = new ArrayList<>();
userRoles.forEach(userRole -> roleIds.add(userRole.getRole().getRoleId()));
List<RoleAccess> roleAccesses = roleAccessRepository.findAllByRoleIdIn(roleIds);
return roleAccesses;
}

/**
* 根据参数查询用户数据
* @return
*/
@Override
public Page<User> findAllUserByParm(String school, String department, String grade, String major, String clazz, String sex, String studentId, String studentName, int page, int limit){
return userRepository.findAllUserByParm(school, department, grade, major, clazz, sex, studentId, studentName,
PageRequest.of(page - 1, limit));
}

/**
* 微信的 code2session 接口 获取微信用户信息
* 官方说明 : https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html
*/
private String code2Session(String jsCode) {
String code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + appSecret + "&js_code=" + jsCode + "&grant_type=authorization_code";
return restTemplate.getForObject(code2SessionUrl, String.class);
}

/**
* 生成随机数
* @param num 位数
* @return
*/
private static String createRandomNum(int num){
String randomNumStr = "";
for(int i = 0; i < num;i ++){
int randomNum = (int)(Math.random() * 10);
randomNumStr += randomNum;
}
return randomNumStr;
}

/**
* 登录更新
* @param account
* @param response
*/
private void loginSuccess(String account,User user,HttpServletResponse response) throws UnsupportedEncodingException {
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
// 清除可能存在的Shiro权限信息缓存
String tokenKey = SecurityConsts.PREFIX_SHIRO_CACHE.getValue() + account;
if (jedisUtils.exists(tokenKey)) {
logger.info("登陆:清除可能存在的Shiro权限信息缓存");
jedisUtils.delKey(tokenKey);
}
// 清除可能存在的登出信息缓存
String logoutKey = SecurityConsts.PREFIX_SHIRO_LOGOUT_TOKEN.getValue() + account;
if (jedisUtils.exists(logoutKey)) {
logger.info("登陆:清除可能存在的登出信息缓存");
jedisUtils.delKey(logoutKey);
}
//更新RefreshToken缓存的时间戳
String refreshTokenKey = SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN.getValue() + account;
if (jedisUtils.exists(refreshTokenKey)) {
logger.info("登陆:更新RefreshToken缓存的时间戳");
jedisUtils.saveString(refreshTokenKey, currentTimeMillis, jwtProperties.getRefreshCheckTime() * 60 * 60);
} else {
logger.info("登陆:新增RefreshToken缓存的时间戳");
jedisUtils.saveString(refreshTokenKey, currentTimeMillis, jwtProperties.getRefreshCheckTime() * 60 * 60);
}
//生成token
String token = JWTUtil.sign(account, currentTimeMillis);
Subject subject = SecurityUtils.getSubject();
subject.login(new JWTToken(token));
logger.info("登陆时subject.isAuthenticated():" + subject.isAuthenticated());
response = CookiesUtil.setCookie(adress,path,response,SecurityConsts.REQUEST_AUTH_HEADER.getValue(), token,3600);
Cookie cookieUserName = new Cookie("userName", user.getUserName());
Cookie cookieImg = new Cookie("imgUrl", user.getImgUrl());
response.addCookie(cookieUserName);
response.addCookie(cookieImg);
}
}

RoleService

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
public interface RoleService {
/**
* 新增角色
* @param role
* @return
*/
BaseVo createNewRole(Role role);

/**
* 修改角色权限
* @param roleAccessVo
* @return
*/
BaseVo updateRoleAccess(RoleAccessVo roleAccessVo);

/**
* 查询所有角色
* @return
*/
List<Role> findAllRole();

/**
* 根据角色ID查询角色
* @return
*/
Role findRoleById(Integer roleId);

/**
* 根据角色ID获取权限
* @param roleId
* @return
*/
List<RoleAccess> findAllRoleAccessByRoleId(Integer roleId);
}
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
@Service
public class RoleServiceImpl implements RoleService {
private RoleRepository roleRepository;
private RoleAccessRepository roleAccessRepository;
private AccessRepository accessRepository;

@Autowired
public RoleServiceImpl(RoleRepository roleRepository, RoleAccessRepository roleAccessRepository, AccessRepository accessRepository) {
this.roleRepository = roleRepository;
this.roleAccessRepository = roleAccessRepository;
this.accessRepository = accessRepository;
}

/**
* 新增角色
* @param role
* @return
*/
@Override
public BaseVo createNewRole(Role role){
role = roleRepository.saveAndFlush(role);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg(),role,1);
}

/**
* 修改角色权限
* @param roleAccessVo
* @return
*/
@Override
public BaseVo updateRoleAccess(RoleAccessVo roleAccessVo){
//先清除角色所有权限
roleAccessRepository.deleteAllByRoleId(roleAccessVo.getRoleId());

List<RoleAccess> roleAccesses = new ArrayList<>();
List<Access> accesses = accessRepository.findAllByAccessIdIn(roleAccessVo.getAccessIds());
if(accesses.size() != roleAccessVo.getAccessIds().size()){
return new BaseVo(1,"权限数据异常");
}
for(Access access : accesses){
RoleAccess roleAccess = new RoleAccess(roleAccessVo.getRoleId(),access);
roleAccesses.add(roleAccess);
}
roleAccessRepository.saveAll(roleAccesses);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg(),roleAccesses,roleAccesses.size());
}

/**
* 查询所有角色
* @return
*/
@Override
public List<Role> findAllRole(){
return roleRepository.findAll();
}

/**
* 根据角色ID查询角色
* @return
*/
@Override
public Role findRoleById(Integer roleId){
return roleRepository.findById(roleId).orElseThrow(DBDataNotFoundException::new);
}

/**
* 根据角色ID获取权限
* @param roleId
* @return
*/
@Override
public List<RoleAccess> findAllRoleAccessByRoleId(Integer roleId){
return roleAccessRepository.findAllByRoleId(roleId);
}
}

AccessService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface AccessService {
/**
* 新增权限
* @param access
* @return
*/
BaseVo createNewAccess(Access access);

/**
* 查询权限列表
* @return
*/
List<Access> findAllAccess();
}
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
@Service
public class AccessServiceImpl implements AccessService {
@Autowired
private RoleAccessRepository roleAccessRepository;
@Autowired
private AccessRepository accessRepository;

/**
* 新增权限
* @param access
* @return
*/
@Override
public BaseVo createNewAccess(Access access){
access = accessRepository.saveAndFlush(access);
return new BaseVo(CodeEnums.SUCCESS.getCode(), CodeEnums.SUCCESS.getMsg(),access,1);
}

/**
* 查询权限列表
* @return
*/
@Override
public List<Access> findAllAccess(){
return accessRepository.findAll();
}
}

SyncCacheService

1
2
3
4
5
public interface ISyncCacheService {
Boolean getLock(String lockName, int expireTime);

Boolean releaseLock(String lockName);
}
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
@Service
public class SyncCacheServiceImpl implements ISyncCacheService {
private static final Logger LOGGER = LoggerFactory.getLogger(SyncCacheServiceImpl.class);

@Autowired
JWTProperties jwtProperties;
@Autowired
JedisUtils jedisUtils;

/**
* 获取redis中key的锁,乐观锁实现
*
* @param lockName
* @param expireTime 锁的失效时间
* @return
*/
@Override
public Boolean getLock(String lockName, int expireTime) {
Boolean result = Boolean.FALSE;
try {
boolean isExist = jedisUtils.exists(lockName);
if (!isExist) {
jedisUtils.getSeqNext(lockName, 0);
jedisUtils.expire(lockName, expireTime <= 0 ? Constants.ExpireTime.ONE_HOUR : expireTime);
}
long reVal = jedisUtils.getSeqNext(lockName, 1);
if (1l == reVal) {
//获取锁
result = Boolean.TRUE;
LOGGER.info("获取redis锁:" + lockName + ",成功");
} else {
LOGGER.info("获取redis锁:" + lockName + ",失败" + reVal);
}
} catch (Exception e) {
LOGGER.error("获取redis锁失败:" + lockName, e);
}
return result;
}

/**
* 释放锁,直接删除key(直接删除会导致任务重复执行,所以释放锁机制设为超时30s)
*
* @param lockName
* @return
*/
@Override
public Boolean releaseLock(String lockName) {
Boolean result = Boolean.FALSE;
try {
jedisUtils.expire(lockName, Constants.ExpireTime.TEN_SEC);
LOGGER.info("释放redis锁:" + lockName + ",成功");
} catch (Exception e) {
LOGGER.error("释放redis锁失败:" + lockName, e);
}
return result;
}
}

控制层

LoginController

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
@RestController
@RequestMapping("/login")
public class LoginController extends BaseController {
@Autowired
private UserService userService;

//登陆接口
@RequestMapping("/loginWeb")
public BaseVo loginWeb(HttpServletResponse response,
@RequestParam("userId") String userId,
@RequestParam("password") String password) throws UnsupportedEncodingException {
return userService.login(userId, password, response);
}

/**
* 退出当前用户登陆
* JWT设置失效时间为当前时间
* @return
*/
@RequestMapping("/logout")
public BaseVo logout(HttpServletRequest request){
return userService.logout(request);
}

/**
* 发送短信验证码
* @param phone 手机号
* @return BaseVo
* @throws ClientException
*/
@RequestMapping(value = "/sendVerificationCode")
public BaseVo sendVerificationCode(String phone) throws ClientException {
return userService.sendVerificationCode(phone);
}

/**
* 微信注册
* 验证用户信息和验证码,并注册用户,成功后登陆用户信息,返回token
* @param wxRegisterVo
* @param response
* @return BaseVo
*/
@RequestMapping(value = "/wxRegister")
public BaseVo wxRegister(WxRegisterVo wxRegisterVo, HttpServletResponse response) throws ClientException,UnsupportedEncodingException {
return userService.wxRegister(wxRegisterVo,response);
}

/**
* wx登录验证ticket,生成本地token,由本地来管理token生命周期
*
* @return
*/
@RequestMapping(value = "/loginWx")
public BaseVo loginWx(String code,HttpServletResponse response) throws UnsupportedEncodingException {
response.setHeader("Access-Control-Allow-Origin", "");
response.setHeader("Access-Control-Allow-Credentials", "true");
return userService.loginWx(code,response);
}

/**
* 获取登录用户基础信息
*
* @return
*/
@RequiresAuthentication
@RequestMapping(value = "/userInfo")
public BaseVo userInfo() {
User user = userService.findByUserId(JWTUtil.getClaim(SecurityUtils.getSubject().getPrincipal().toString(), SecurityConsts.USERID.getValue()));
// //查询菜单
// List<ResourceNode> menus = resourceService.findByUserId(user.getId());
// //查询权限
// List<Object> authorityList = authorityService.findByUserId(user.getId());
return response(user, 1);

}

//所有人都可以访问,但是用户与游客看到的内容不同
@GetMapping("/article")
public BaseVo article() {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
return new BaseVo(200, "You are already logged in", null);
} else {
return new BaseVo(200, "You are guest", null);
}
}

//登入的用户才可以进行访问
@GetMapping("/require_auth")
@RequiresAuthentication
public BaseVo requireAuth() {
return new BaseVo(200, "You are authenticated", null);
}

//admin的角色用户才可以登入
@GetMapping("/require_role")
@RequiresRoles("admin")
public BaseVo requireRole() {
return new BaseVo(200, "You are visiting require_role", null);
}

//拥有view和edit权限的用户才可以访问
@GetMapping("/require_permission")
@RequiresPermissions(logical = Logical.AND, value = {"view", "edit"})
public BaseVo requirePermission() {
return new BaseVo(200, "You are visiting permission require edit,view", null);
}

@RequestMapping(path = "/401")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public BaseVo unauthorized() {
return new BaseVo(401, "Unauthorized", null);
}
}

UserController

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
@RestController
@RequestMapping("user")
public class UserController extends BaseController {
@Autowired
private UserService userService;

/**
* 新增用户
* @return
*/
@PostMapping("/createNewUser")
public BaseVo createNewUser(String userId,
@RequestParam(required = false) String openid,
String pwd,
String userName,
String nickName,
String school,
String department,
String major,
String classname,
Integer grade,
String sex,
String address,
String phone,
String imgUrl) {
java.sql.Timestamp regTime = new Timestamp(System.currentTimeMillis());
return userService.createNewUser(new User(userId,
openid,
pwd,
userName,
nickName,
school,
department,
major,
classname,
grade,
sex,
regTime,
address,
phone,
imgUrl));
}


/**
* 修改用户密码
*
* @param userPassword
* @return
*/
@RequiresAuthentication
@PutMapping("/changePwd")
public BaseVo changePwd(UserPasswordVo userPassword) {
return userService.changePwd(userPassword);
}

/**
* 修改用户信息
* 性别,昵称等
*/
@RequiresAuthentication
@PutMapping("/changeUserInfo")
public BaseVo changeUserInfo(String nickName, String address,String imgUrl) {
return userService.changeUserInfo(JWTUtil.getClaim(SecurityUtils.getSubject().getPrincipal().toString(), SecurityConsts.USERID.getValue()),
nickName, address,imgUrl);
}

/**
* 根据用户ID查询其所有权限
* @return
*/
@RequiresAuthentication
@RequestMapping(value = "/findAllRoleAccessByUserId", method = RequestMethod.POST)
public BaseVo findAllRoleAccessByUserId() {
User user = userService.findByUserId(JWTUtil.getClaim(SecurityUtils.getSubject().getPrincipal().toString(), SecurityConsts.USERID.getValue()));
List<RoleAccess> views = userService.findAllRoleAccessByUserId(user.getUserId());
return response(views, views.size());
}

/**
* 根据参数查询用户数据
* @return
*/
@RequiresAuthentication
@RequestMapping(value = "/findAllUserByParm", method = RequestMethod.POST)
public BaseVo findAllUserByParm(String school, String department, String grade, String major, String className, String sex, String studentId, String studentName, int page, int limit) {
Page<User> views = userService.findAllUserByParm(school,department,grade,major,className,sex,studentId,studentName,page,limit);
//当前结果合集
List<User> content = views.getContent();
//结果总行数
int size = (int) views.getTotalElements();
JSONArray jsonArray = new JSONArray();
content.forEach(apply->jsonArray.add(apply));
return response(jsonArray, size);
}
}