英文:
Spring security when I try to get jwt token second time, it returns forbidden
问题
I enabled spring security. I have 2 users in my userDetails service. I have an authentication endpoint /api/v1/auth/authenticate
. This endpoint is not secured.
I start the application and hit send button in postman. It returns the JWT token. When I click send right after, it returns 403
.
After these steps, I entered the credentials of the second user in postman request. I hit the send request button. JWT
token returned normally. When I clicked send button the second time, it returns 403
.
I am able to access secured endpoints normally using the JWT
.
I restarted the server, Again I am able to call and get JWT
token but I can get it only once, the other attempts to generate JWT
token results in a 403
response.
When I debugged the application, it gets bad credentials exception in this line
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(), request.getPassword()
)
);
it doesn't print the exception to the console. Even if the credentials are correct (I don't change request body between the first and second attempt to get JWT.) the response is 403 in the second attempt.
Why is that?
I use spring boot 3.1.0
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
my userdetails service is:
@Component
public class MyUserDetailsService implements UserDetailsService {
private final static List<UserDetails> APPLICATION_USERS = Arrays.asList(
new User(
"ylozsoy@gmail.com",
"password",
Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"))
),
new User(
"yilmazozsoy@gmail.com",
"password2",
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
)
);
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return APPLICATION_USERS
.stream()
.filter(u -> u.getUsername().equals(email))
.findFirst()
.orElseThrow(() -> new UsernameNotFoundException("No user was found for email: " + email));
}
}
It is breaking here
image
At the first call this looks like :
image
At the second call, password is gone
image
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(AUTHORIZATION);
String userEmail;
String jwtToken;
if (authHeader == null || !authHeader.startsWith("Bearer")) {
filterChain.doFilter(request, response);
return;
}
jwtToken = authHeader.substring(7);
userEmail = jwtService.extractUsername(jwtToken);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if(jwtService.isTokenValid(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
@PostMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestBody AuthenticationRequest request){
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())
);
final UserDetails user = userDetailsService.loadUserByUsername(request.getEmail());
if (user != null) {
return ResponseEntity.ok(jwtService.generateToken(user));
}
return ResponseEntity.status(400).body("Some error has occurred");
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf((csrf) -> csrf.disable());
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authorizeHttpRequests((requests) -> {
requests.requestMatchers(antMatcher("/api/v1/auth/**"))
.permitAll()
.anyRequest()
.authenticated();
});
http.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
pom.xml;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springbootdemoweb</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.5-b10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<details>
<summary>英文:</summary>
I enabled spring security. I have 2 users in my userDetails service. I have an authentication endpoint ```/api/v1/auth/authenticate```. This endpoint is not secured.
I start the application and hit send button in postman. It returns the JWT token. When I click send right after, it returns ```403```.
After these steps, I entered the credentials of the second user in postman request. I hit the send request button. ```JWT``` token returned normally. When I clicked send button the second time, it returns ```403```.
I am able to access secured endpoints normally using the ```JWT```.
I restarted the server, Again I am able to call and get ```JWT``` token but I can get it only once, the other attempts to generate ```JWT``` token results in a ```403``` response.
When I debugged the application, it gets bad credentials exception in this line
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(), request.getPassword()
)
);
it doesn't print the exception to the console. Even if the credentials are correct (I don't change request body between the first and second attempt to get JWT.) the response is 403 in the second attempt.
Why is that?
I use spring boot 3.1.0
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
my userdetails service is:
```java
@Component
public class MyUserDetailsService implements UserDetailsService {
private final static List<UserDetails> APPLICATION_USERS = Arrays.asList(
new User(
"ylozsoy@gmail.com",
"password",
Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"))
),
new User(
"yilmazozsoy@gmail.com",
"password2",
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
)
);
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return APPLICATION_USERS
.stream()
.filter(u -> u.getUsername().equals(email))
.findFirst()
.orElseThrow(() -> new UsernameNotFoundException("No user was found for email: " + email));
}
}
It is breaking here
image
At the first call this looks like :
image
At the second call, password is gone
image
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(AUTHORIZATION);
String userEmail;
String jwtToken;
if (authHeader == null || !authHeader.startsWith("Bearer")) {
filterChain.doFilter(request, response);
return;
}
jwtToken = authHeader.substring(7);
userEmail = jwtService.extractUsername(jwtToken);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if(jwtService.isTokenValid(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
@PostMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestBody AuthenticationRequest request){
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())
);
final UserDetails user = userDetailsService.loadUserByUsername(request.getEmail());
if (user != null) {
return ResponseEntity.ok(jwtService.generateToken(user));
}
return ResponseEntity.status(400).body("Some error has occured");
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf((csrf) -> csrf.disable());
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authorizeHttpRequests((requests) -> {
requests.requestMatchers(antMatcher("/api/v1/auth/**"))
.permitAll()
.anyRequest()
.authenticated();
});
http.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
pom.xml;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springbootdemoweb</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.5-b10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
答案1
得分: 1
Spring Security 避免在内存中保存凭据,如密码,时间尽量短。这是通过 org.springframework.security.core.userdetails.User
实现的 eraseCredentials()
方法来实现的。您的 User
列表保存在静态字段中,因此两个 User
实例被重复使用。一旦凭据被擦除,这会使 User
无效。
与创建自己的内存中的 UserDetailsService
实现不同,我建议使用 Spring Security 的 InMemoryUserDetailsManager
。
英文:
Spring Security avoids holding credentials, such as a password, in memory for any longer than necessary. This is implemented using the eraseCredentials()
method that org.springframework.security.core.userdetails.User
implements. Your list of User
s is held in a static field so the two User
instances are re-used. Once the credentials have been erased, this makes the User
useless.
Rather than creating your own in-memory UserDetailsService
implementation, I would use Spring Security's InMemoryUserDetailsManager
instead.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论