使用 jwt 来验证 springboot3
文件如下:
build.gradle.kts
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
|
plugins {
java
id("org.springframework.boot") version "3.1.4"
id("io.spring.dependency-management") version "1.1.3"
}
group = "com.sky"
version = "0.0.1-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
// implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
//add jwt
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
implementation("org.projectlombok:lombok:1.18.22")
compileOnly("org.projectlombok:lombok:1.18.22")
annotationProcessor("org.projectlombok:lombok:1.18.22")
implementation("mysql:mysql-connector-java:8.0.33")
}
tasks.withType<Test> {
useJUnitPlatform()
}
|
springboot3 出现的循环依赖如何解决了
1
2
|
#允许循环依赖
spring.main.allow-circular-references=true
|
更新:
第一种、开启循环依赖(不推荐)
Spring 默认已经不在支持循环依赖,在配置文件中重新开启循环依赖支持
1
|
spring:`` ``main:`` ``allow-circular-references:true #允许循环引用
|
第二种、懒加载
@Lazy:配合使用该注解可以解决循环依赖问题(在需要注入 Bean 的地方加上该注解)
1
|
@Lazy` `// 使用懒加载``@Autowired``private` `OneService oneService;
|
第三种、控制反转
1
|
@Service``@RequiredArgsConstructor` `// 该注解的使用在下面会有介绍和说明``public` `class` `OneServiceImpl ``implements` `OneService {`` ``private` `final` `ConfigurableListableBeanFactory beanFactory;`` ``//代替循环依赖`` ``public` `TwoService getTwoService(){`` ``return` `beanFactory.getBean(TwoService.``class``);`` ``}``}
|
使用getTwoService()
直接从 bean 工厂里面去拿对应的 Bean 来使用
1
|
@Service``@RequiredArgsConstructor` `// 该注解的使用在下面会有介绍和说明``public` `class` `TwoServiceImpl ``implements` `TwoService {`` ``private` `final` `ConfigurableListableBeanFactory beanFactory;`` ``//代替循环依赖`` ``public` `OneService getOneService(){`` ``return` `beanFactory.getBean(OneService.``class``);`` ``}``}
|
以上代码中,OneService 依赖于 TwoService,而 TwoService 也依赖于 OneService,从而产生循环依赖
解决:每次使用的时候就去 Bean 工厂里去获取,这样就不存在循环依赖了
@RequiredArgsConstructor 使用说明
@RequiredArgsConstructor
:该注解是由Lombok
提供,可以解决掉大量重复的@Autowired
代码
注意:使用@RequiredArgsConstructor
时,需要使用final
关键字
写在类上可以代替@Autowired 注解,需要注意的是在注入时需要用 final 定义,或者使用@notnull 注解
1
|
@RestController``@RequiredArgsConstructor` `// 代替@Autowired注解``@RequestMapping``(``"/api/v1/one"``)``public` `class` `OneController{`` ``private` `final` `OneService oneService; ``// 需要final关键字`` ``private` `final` `TwoService twoService; ``// 需要final关键字`` ``@GetMapping``(``"{id}"``)`` ``public` `ResultVo<String> getDetails(``@PathVariable``(``"id"``) Long id){`` ``return` `ResultVo.ok(oneService.getDetailsById(id));`` ``}``}
|
注意点:
1、必须声明的变量为 final。
2、根据构造器注入的,相当于容器调用带有一组带有参数的类构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他类的依赖。基于构造方法为属性赋值,容器通过调用类的构造方法将其进行依赖注入。
3、当需要注入 Bean 的时候可以直接在类名称上使用@RequiredArgsConstructor,从而代替了大量的@Autowrited 注解。
找到实现方式
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
|
package com.truongbn.security.service.impl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.truongbn.security.dao.request.SignUpRequest;
import com.truongbn.security.dao.request.SigninRequest;
import com.truongbn.security.dao.response.JwtAuthenticationResponse;
import com.truongbn.security.entities.Role;
import com.truongbn.security.entities.User;
import com.truongbn.security.repository.UserRepository;
import com.truongbn.security.service.AuthenticationService;
import com.truongbn.security.service.JwtService;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor //这个类解决了循环依赖。
public class AuthenticationServiceImpl implements AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
@Override
public JwtAuthenticationResponse signup(SignUpRequest request) {
var user = User.builder().firstName(request.getFirstName()).lastName(request.getLastName())
.email(request.getEmail()).password(passwordEncoder.encode(request.getPassword()))
.role(Role.USER).build();
userRepository.save(user);
var jwt = jwtService.generateToken(user);
return JwtAuthenticationResponse.builder().token(jwt).build();
}
@Override
public JwtAuthenticationResponse signin(SigninRequest request) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));
var user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new IllegalArgumentException("Invalid email or password."));
var jwt = jwtService.generateToken(user);
return JwtAuthenticationResponse.builder().token(jwt).build();
}
}
|
注意还是需要把依赖的一个选项替换到其他位置。
参考文档:
https://github.com/buingoctruong/springboot3-springsecurity6-jwt/tree/master
记录一下 websecurity 写法
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
|
package com.sky.fullstack.config;
import com.sky.fullstack.filter.JwtAuthFilter;
import com.sky.fullstack.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Autowired
private JwtAuthFilter authFilter;
// User Creation
@Bean
public UserDetailsService userDetailsService() {
return new UserInfoService();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// http.cors(withDefaults())
// .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable())
// .authorizeHttpRequests((requests) -> requests
//// .requestMatchers("/", "/home").permitAll()
// .requestMatchers("/auth/addNewUser").permitAll()
// .requestMatchers("/auth/generateToken").permitAll()
// .requestMatchers("auth/welcome").permitAll()
// .anyRequest().authenticated()
//
// ).sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// ).authenticationProvider(authenticationProvider())
// .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
// ;
http.cors(withDefaults())
.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable())
// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
// 前后端分离是无状态的,不需要session了,直接禁用。
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
// 允许所有OPTIONS请求
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/", "/home").permitAll()
// 允许直接访问授权登录接口
.requestMatchers(HttpMethod.POST, "/auth/generateToken").permitAll()
.requestMatchers("/auth/addNewUser").permitAll()
// 允许 SpringMVC 的默认错误地址匿名访问
.requestMatchers("/error").permitAll()
// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
// 允许任意请求被已登录用户访问,不检查Authority
.anyRequest().authenticated())
.authenticationProvider(authenticationProvider())
// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
// @Bean
// public UserDetailsService userDetailsService() {
// UserDetails user =
// User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
//
// return new InMemoryUserDetailsManager(user);
// }
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
// Password Encoding
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
|
参考文档:
https://www.geeksforgeeks.org/spring-boot-3-0-jwt-authentication-with-spring-security-using-mysql-database/