1、概述

在现代Web应用开发中,安全是一个至关重要的方面。本文将介绍如何将Spring Boot 3、PostgreSQL数据库、JPA、Spring Security 6.3、Redis和JWT整合在一起,实现一个安全的用户认证和授权系统。

2、项目主要环境

  • Spring Boot: 3.3.1
  • PostgreSQL: 16.1
  • Spring Security: 6.3
  • Redis: 5.0.14.1
  • JWT: 0.12.3

3、Spring Security 6与JWT的介绍

Spring Security 6是Spring框架中用于安全性的核心库,它提供了全面的安全服务,包括认证、授权、防止常见攻击等。JWT(JSON Web Tokens)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输声明(claims)。

Spring Security 6

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它主要专注于为Java应用程序提供身份验证和授权。Spring Security 6是该框架的最新版本,它提供了许多改进和新特性,比如对现代安全威胁的增强防护、更灵活的配置选项、更好的性能等。

Spring Security 6的核心概念包括:

  • 认证(Authentication):验证用户身份的过程。这是确定用户是否是他们所声称的那个人的过程。
  • 授权(Authorization):在用户被认证之后,授权过程决定用户是否有权限执行特定的操作。
  • 保护(Protection):保护应用免受攻击,例如CSRF(跨站请求伪造)、XSS(跨站脚本攻击)等。

JWT

JWT是一种紧凑的、自包含的方式,用于在各方之间以JSON对象的形式安全传输信息。JWT可以使用HMAC算法或使用RSA的公钥/私钥对进行签名。

JWT通常用于身份验证和信息交换,特别是在Web应用中,作为客户端和服务器之间传递安全信息的手段。一个JWT实际上是一个被编码的字符串,包含三个部分:

  • Header(头部):通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。
  • Payload(有效载荷):包含所要传递的数据,这些数据是关于实体(通常是用户)的声明(claims)。声明可以是注册的(registered)声明,也可以是公开的(public)声明,或者是私有的(private)声明。
  • Signature(签名):为了创建签名部分,你必须有编码后的header、编码后的payload、一个密钥、header中指定的算法进行签名。签名用于验证消息在此过程中未被篡改。

Spring Security 6与JWT整合

在Spring Security 6中整合JWT,通常涉及以下步骤:

  1. 配置Spring Security:设置安全规则,允许对登录端点的匿名访问,而其他所有请求都需要认证。

  2. 用户详情服务:实现UserDetailsService接口,用于从数据库加载用户信息,并提供密码的加密和解密。

  3. JWT工具类:创建一个工具类来生成和验证JWT。这个类将包含生成JWT的逻辑,以及一个方法来从JWT中提取用户名。

  4. 登录控制器:创建一个控制器来处理用户的登录请求。登录控制器将验证用户凭证,然后生成JWT作为响应。

  5. 认证拦截器:创建一个拦截器,拦截进入的请求,并检查JWT的有效性。

整合JWT和Spring Security 6可以提供一个安全、高效且易于扩展的身份验证机制。通过这种方式,你可以保护你的Web应用,确保只有经过授权的用户才能访问敏感资源。

4、认证过程

在Web应用中,认证过程是验证用户身份并授权用户访问受保护资源的关键步骤。以下是使用Spring Security和JWT进行认证的一般步骤:

1. 用户提交认证信息

用户通过登录表单或API端点提交他们的认证信息,通常包括用户名和密码。

2. 认证请求到达过滤器

在Spring Security中,所有的HTTP请求都会被过滤器链拦截。对于登录请求,通常会有一个专门的过滤器(例如UsernamePasswordAuthenticationFilter)来处理用户名和密码。

3. 过滤器验证凭证

过滤器会调用AuthenticationManager来验证用户提交的凭证。AuthenticationManager是一个接口,它定义了一个方法authenticate,用于执行实际的认证逻辑。

4. 用户详情服务检索用户信息

AuthenticationManager通常会委托给一个AuthenticationProvider,后者会调用UserDetailsService来根据用户名检索用户信息。UserDetailsService会返回一个UserDetails对象,其中包含了用户信息和权限。

5. 凭证验证

AuthenticationProvider会比较从数据库中检索到的用户密码(通常需要解密或验证)和用户提交的密码。如果密码匹配,AuthenticationProvider会创建一个Authentication对象,该对象包含了用户认证成功后的详细信息,如用户的角色和权限。

6. 生成JWT

一旦用户通过认证,系统会生成一个JWT。这个JWT通常包含用户的身份信息(如用户ID)和一些元数据(如过期时间)。然后,使用一个密钥对JWT进行签名,以确保其完整性和真实性。

7. 返回JWT给客户端

认证成功后,服务器会将生成的JWT作为响应的一部分返回给客户端。客户端(如浏览器或移动应用)需要将这个JWT存储起来,通常是在HTTP请求的Authorization头部中以Bearer模式发送。

8. 客户端发送JWT进行后续请求

对于需要认证的后续请求,客户端需要在HTTP请求的Authorization头部中包含JWT。例如:

1
Authorization: Bearer <your-jwt-token>

9. 服务器端验证JWT

服务器端的一个拦截器(如JwtInterceptor)会拦截请求,并从Authorization头部中提取JWT。然后,拦截器会验证JWT的有效性,包括签名、过期时间等。

5、实战

1、准备工作

搭建一个SpringBoot工程

1.1、项目结构

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
src/
|-- main/
| |-- java/
| | |-- com.yourcompany.project/
| | | |-- controller/
| | | | |-- UserController.java
| | | |-- entity/
| | | | |-- User.java
| | | | |-- Result.java
| | | | |-- MyUserDetails.java
| | | |-- repository/
| | | | |-- UserRepository.java
| | | |-- service/
| | | | |-- impl/
| | | | | |-- UserServiceImpl.java
| | | | |-- UserService.java
| | | |-- config/
| | | | |-- WebSecurityConfig.java
| | | | |-- WebConfig.java
| | | | |-- RedisConfig.java
| | | |-- util/
| | | | |-- JwtUtil.java
| | | | |-- RedisUtil.java
| | | |-- interceptor/
| | | | |-- JwtInterceptor.java

2、添加依赖

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
<!-- SpringSecurity依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JPA依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.3</version>
</dependency>
<!-- Lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

3、配置文件application.yml

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
spring:
# PostgresSQL配置
datasource:
url: jdbc:postgresql://127.0.0.1:5432/test # 配置数据库
username: natuie
password: xxxxxx
driver-class-name: org.postgresql.Driver
jpa:
show-sql: true # 在控制台打印执行的SQL语句
hibernate:
ddl-auto: update # 指定为update,每次启动项目因表结构变化就会更新/新增字段,表不存在时会新建,如果指定create,则每次启动项目都会清空数据并删除表,再新建

# Redis配置
data:
redis:
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接池最大连接数(使用负值表示没有限制)
pool:
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接超时时间(毫秒)
timeout: 30000
jackson:
#日期类型格式化
date-format: yyyy-MM-dd HH:mm:ss

jwt:
secret: rlFgiCr2rRMch/bTWYbz=p8IY/Rj8oQdcu2zpj1/SQzrE= # 密钥,一定要>=256bit,不是256长度,>=32位长度就行,不然报错
expiration: 259200000 # 3天过期时间

4、创建Controller:处理HTTP请求,如用户注册和登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("/auth")
public class UserController {

private final UserService userService;

@Autowired
public UserController(UserService userService){
this.userService = userService;
}

@PostMapping("/login")
public Result<Object> login(User user){
return userService.login(user);
}

@PostMapping("/register")
public Result<Object> register(User user){
return userService.register(user);
}
}

5、创建User实体类:定义用户实体,包含用户信息如用户名、密码等

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
@NoArgsConstructor
@Entity
@Table(name = "userdata")
@Data
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // ID 是自增的
private Long id; // 主键

@Column(length = 18, nullable = false, unique = true)
private String account; // 账号

@Column(length = 64, nullable = false)
private String password; // 密码

@Column(length = 13, unique = true)
private String mobileNumber; // 手机号

@Column(length = 128, nullable = false,unique = true)
private String email; // 邮箱

@Column(length = 36, nullable = false)
private String username; // 用户名

@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date registerTime; // 注册时间

private String identity; // 身份信息,会员等

private String state; // 状态,正常、冻结等

private Short sex; // 性别,0:中,1:男,2:女

@Temporal(TemporalType.DATE)
private Date birthDate; // 生日

private String address; // 地址

private BigDecimal money; // 余额

@Column(length = 255)
private String registerInfo; // 注册信息
}

6、创建Result实体类:用于数据返回

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
@JsonInclude(JsonInclude.Include.NON_NULL) // Ignore null values when serializing to JSON
@Data
@Setter
@Getter
@ToString
public class Result<T> {
private int code;
private String message;
private T data;

public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}

public static <E> Result<E> success(String message) {
return new Result<>(200, message, null);
}

public static <E> Result<E> success(int code, String message) {
return new Result<>(code, message, null);
}

public static <E> Result<E> success(E data) {
return new Result<>(200, null, data);
}

public static <E> Result<E> success(String message, E data) {
return new Result<>(200, message, data);
}

public static <E> Result<E> error(String message) {
return new Result<>(201, message, null);
}

public static <E> Result<E> error(int code, String message) {
return new Result<>(code, message, null);
}
}

7、创建JpaRepository接口:用于访问数据库中的用户数据

1
2
3
public interface UserRepository extends JpaRepository<User, Integer> {
User findByAccount(String account);
}

8、自定义UserDetailsService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class MyUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public MyUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("loadUserByUsername: " + username);
User user = userRepository.findByAccount(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new MyUserDetails(user);
}
}

9、创建用户业务UserService接口:定义用户服务接口

1
2
3
4
public interface UserService {
Result<Object> login(User user);
Result<Object> register(User user);
}

10、创建用户业务实现UserServiceImpl接口:实现用户服务接口,处理用户注册和登录逻辑

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
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Service
public class UserServiceImpl implements UserService {

private final AuthenticationManager authenticationManager;

private final RedisUtil redisUtil;

private final JwtUtil jwtUtil;

private final UserRepository userRepository;

@Autowired
public UserServiceImpl(AuthenticationManager authenticationManager, RedisUtil redisUtil,
JwtUtil jwtUtil, UserRepository userRepository) {
this.authenticationManager = authenticationManager;
this.redisUtil = redisUtil;
this.jwtUtil = jwtUtil;
this.userRepository = userRepository;
}

@Override
public Result<Object> login(@RequestBody User user) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(user.getAccount(),user.getPassword());
try {
// 认证
Authentication authenticate = authenticationManager.authenticate(authenticationToken);

// 生成JWT
MyUserDetails loginUser = (MyUserDetails) authenticate.getPrincipal();
String jwt = jwtUtil.generateToken(loginUser.getUser());

// 保存到Redis
Map<String,String> map = Map.ofEntries(Map.entry("token", jwt));
redisUtil.hmset("TOKEN_" + jwt, map, jwtUtil.getExpirationTime());
return Result.success("登陆成功!", jwt);
} catch (BadCredentialsException e) {
// 凭证无效,返回错误信息
return Result.error("凭据无效!");
} catch (AuthenticationException e) {
// 其他认证错误,返回错误信息
return Result.error("认证失败!");
}

}

@Override
public Result<Object> register(@RequestBody User user) {
// 可以写一些检测逻辑,比如账号是否已存在,密码是否符合要求等等

User newUser = new User();
newUser.setAccount(user.getAccount());
newUser.setPassword(user.getPassword());
newUser.setEmail(user.getEmail());
newUser.setRegisterTime(new Date());
userRepository.save(newUser);
return Result.success("注册成功!");
}

11、创建WebSecurityConfig配置文件:配置Spring Security,定义安全规则

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

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
// 放行登录,注册页面
.requestMatchers("/auth/login", "/auth/register").permitAll()
// 拦截其他所有请求
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManager(MyUserDetailsService userService, PasswordEncoder passwordEncoder) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userService);
provider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(provider);
}

}

12、创建WebConfig配置文件:配置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class WebConfig implements WebMvcConfigurer {

private final JwtInterceptor jwtInterceptor;

@Autowired
public WebConfig(JwtInterceptor jwtInterceptor) {
this.jwtInterceptor = jwtInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**") // 拦截的请求
.excludePathPatterns("/auth/login", "/auth/register"); // 不拦截的请求
}
}

13、创建JwtInterceptor过滤器::拦截请求并验证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
@Component
public class JwtInterceptor implements HandlerInterceptor {

private final JwtUtil jwtUtil;

public JwtInterceptor(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
String token = request.getHeader("Authorization"); // 获取请求头中的token
try {
jwtUtil.parseJWT(token);
} catch (SignatureException e) {
return unauthorized(request, response, Result.error("无效签名"));
} catch (UnsupportedJwtException e) {
return unauthorized(request, response, Result.error("不支持的签名"));
} catch (ExpiredJwtException e) {
return unauthorized(request, response, Result.error("token过期"));
} catch (MalformedJwtException e) { // IllegalArgumentException
return unauthorized(request, response, Result.error("不支持的签名格式"));
} catch (Exception e) {
return unauthorized(request, response, Result.error("token无效"));
}
}
return true; // 通过所有OPTION请求
}

private boolean unauthorized(HttpServletRequest request, HttpServletResponse response, Result<String> result) throws Exception {
String json = new ObjectMapper().writeValueAsString(result);
response.setStatus(401);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}

14、创建RedisConfig配置文件:添加Bean用于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
29
30
31
32
33
34
@Configuration
public class RedisConfig {

@Bean("redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);


//Json序列化配置
Jackson2JsonRedisSerializer<Object> Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mp = new ObjectMapper();
mp.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mp.activateDefaultTyping(mp.getPolymorphicTypeValidator());
Jackson2JsonRedisSerializer.serialize(mp);

//Spring的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value的序列化方式采用的是jackson
template.setValueSerializer(Jackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashValueSerializer(Jackson2JsonRedisSerializer);
// 设置其他的k-v的默认的序列化
template.setDefaultSerializer(Jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}


}

15、创建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
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
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public final class RedisUtil<K, V> {

private final RedisTemplate redisTemplate;

@Autowired
public RedisUtil(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}


/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}


// ============================String=============================

/**
* 普通缓存获取
*
* @param key 键
* @return
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/

public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/

public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}


/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}


// ================================Map=================================

/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<K, V> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<?, ?> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<?, ?> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);


if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}


/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}


/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}


/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}


// ============================set=============================

/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 获取set缓存的长度
*
* @param key 键
*/
public long getSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/

public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

// ===============================list=================================

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 获取list缓存的长度
*
* @param key 键
*/
public long getListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}


/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}


/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/

public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/

public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}

}

}

16、创建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
@Component
@Getter
public class JwtUtil {

private String secretKey;

private int expirationTime; // 3 days

@Value("${jwt.secret}")
private void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

@Value("${jwt.expiration}")
private void setExpirationTime(int expirationTime) {
this.expirationTime = expirationTime;
}

public SecretKey key() {
return Keys.hmacShaKeyFor(secretKey.getBytes());
}

public String generateToken(User user) {
System.out.println(secretKey);
return Jwts.builder()
.id(String.valueOf(user.getId()))
.subject(user.getUsername()) // 主题
.issuedAt(new Date()) // 签发时间
.issuer("Natuie")
.claim("account", user.getAccount()) // 账号
.claim("email", user.getEmail()) // 邮箱
.expiration(new Date(System.currentTimeMillis() + expirationTime)) // 过期时间
.signWith(key()) // 签名
.compact(); // 压缩
}

public Jws<Claims> parseJWT(String token) {
return Jwts.parser()
.verifyWith(key()).build()
.parseSignedClaims(token); // 验证并解析
}

public boolean validateToken(String token) {
try {
parseJWT(token);
return true;
} catch (ExpiredJwtException e) {
// Token过期
return false;
}
}
}

6、运行

发送POST请求到/auth/register然后请求到/auth/login,可以发现返回JWT数据,那就成功了。


本站由 Natuie 使用 Stellar 1.26.8 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站总访问量次 | 本站总访客数人次
载入天数...载入时分秒...