i tried many time to discover where is the probleme in ma code , but i cannot get it . normally when the user do log out the value of disable and expire should be 1 (true) . nothing is changing when i searched i found that this error
2024-06-04T16:29:08.610+01:00 DEBUG 21500 --- [login] [io-8080-exec-10] o.s.security.web.FilterChainProxy : Securing POST /logout
2024-06-04T16:29:08.610+01:00 DEBUG 21500 --- [login] [io-8080-exec-10] o.s.s.w.a.logout.LogoutFilter : Logging out [null]
means that no user in ma SecurityContextHolder ! whyy ?
i really appreciate it if someone could help me
i will share with you my code :
**
this is my user entity
**
@Entity
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class UserApp implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NonNull
private String email;
@NonNull
@JsonProperty(access= JsonProperty.Access.WRITE_ONLY)
private String password;
@NonNull
@Enumerated(EnumType.STRING)
private Role role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(role.toString()));
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
this is my jwt entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "jwt")
public class Jwt {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String val;
private boolean disable;
private boolean expire;
@ManyToOne(cascade = {CascadeType.DETACH ,CascadeType.MERGE})
@JoinColumn(name="user_id")
private UserApp userApp;
}
this is my jwt entity
package com.login.services;
import com.login.entity.Jwt;
import com.login.entity.UserApp;
import com.login.repository.JwtRepository;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Transactional
@Service
public class JwtService {
public static final String BEARER="Bearer";
private UserService userService;
private JwtRepository jwtRepository;
private final String KEY_VALUE="adfec4ddac0a73857deee0be16be519588f12dbd73d69353849d61725ad3ac98";
public JwtService(UserService userService, JwtRepository jwtRepository) {
this.userService = userService;
this.jwtRepository = jwtRepository;
}
public Map<String, String> generateToken(String email) {
UserApp userApp = (UserApp) this.userService.loadUserByUsername(email);
this.disableTokens(userApp);
Map<String, String> jwtString = this.generateJwt(userApp);
Jwt jwt = Jwt.builder().val(jwtString.get("Bearer")).disable(false).expire(false).userApp(userApp).build();
log.info("Saving JWT token for user: {}", email);
Jwt savedJwt = this.jwtRepository.save(jwt);
log.info("Saved JWT token with ID: {} and value: {}", savedJwt.getId(), savedJwt.getVal());
return jwtString;
}
private void disableTokens(UserApp userApp) {
final List<Jwt> jwtList=this.jwtRepository.findUserApp(userApp.getEmail()).peek(
jwt -> {
jwt.setDisable(true);
jwt.setExpire(true);
}
).collect(Collectors.toList());
this.jwtRepository.saveAll(jwtList);
}
private Map<String, String> generateJwt(UserApp userApp) {
final long currentTimeMillis=System.currentTimeMillis();
final long expirationTime=currentTimeMillis+2*60*60*100;
Map<String,Object> claims= Map.of(
"role",userApp.getRole().toString(),
Claims.EXPIRATION,new Date(expirationTime),
Claims.SUBJECT,userApp.getEmail()
);
String bearer= Jwts.builder()
.setIssuedAt( new Date(currentTimeMillis))
.setExpiration(new Date(expirationTime))
.setSubject(userApp.getEmail())
.setClaims(claims)
.signWith(getKey(), SignatureAlgorithm.HS256)
.compact();
return Map.of(BEARER,bearer);
}
private Key getKey(){
final byte[] decoder= Decoders.BASE64.decode(KEY_VALUE);
return Keys.hmacShaKeyFor(decoder);
}
public String extractEmail(String token) {
return this.getClaim(token, Claims::getSubject);
}
public boolean isTokenExpired(String token) {
Date expirationDate = this.getClaim(token, Claims::getExpiration);
boolean expired = expirationDate.before(new Date());
log.info("Token expired: {}", expired);
return expired;
}
private <T> T getClaim(String token, Function<Claims,T> function){
Claims claims=getAllClaim(token);
return function.apply(claims);
}
private Claims getAllClaim(String token){
return Jwts.parserBuilder()
.setSigningKey(this.getKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public Jwt tokenByValue(String value) {
log.info("Fetching token by value: {}", value);
Jwt jwt = this.jwtRepository.findByVal(value).orElse(null);
if (jwt == null) {
log.warn("Token not found for value: {}", value);
}
return jwt;
}
public void logout() {
UserApp userApp = (UserApp) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("Logging out user: {}", userApp.getEmail());
Jwt jwt = this.jwtRepository.findUserAppValidToken(userApp.getEmail(), false, false)
.orElseThrow(() -> {
log.error("Valid token not found for user: {}", userApp.getEmail());
return new RuntimeException("Invalid token");
});
this.jwtRepository.delete(jwt);
//log.info("Disabling token: {}", jwt.getVal());
//jwt.setDisable(true);
// jwt.setExpire(true);
//this.jwtRepository.save(jwt);
//log.info("Token disabled and expired successfully for user: {}", userApp.getEmail());
}
public void removeUselessJwt(){
}
}
this is my user service
package com.login.services;
import com.login.entity.UserApp;
import com.login.repository.AppUserRepository;
import jakarta.transaction.Transactional;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@Transactional
public class UserService implements UserDetailsService {
private AppUserRepository userRepository;
private BCryptPasswordEncoder passwordEncoder;
public UserService(AppUserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public UserApp addNewUser(UserApp userApp){
Optional<UserApp> userAppOpt=this.userRepository.findByEmail(userApp.getEmail());
if(userAppOpt.isPresent()){
throw new RuntimeException("this email exist before");
}
userApp.setPassword(passwordEncoder.encode(userApp.getPassword()));
return this.userRepository.save(userApp);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.userRepository.findByEmail(username).orElseThrow(()->new UsernameNotFoundException("no user with this email is found"));
}
}
}
this is my filter entity
package com.login.security;
import com.login.entity.Jwt;
import com.login.services.JwtService;
import com.login.services.UserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
@Service
public class JwtFilter extends OncePerRequestFilter {
private final UserService userService;
private final JwtService jwtService;
public JwtFilter(UserService userService, JwtService jwtService) {
this.userService = userService;
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Bearer ")) {
String token = authorization.substring(7);
Jwt jwtInDb = jwtService.tokenByValue(token);
log.info("Authorization header found, token: {}", token);
if (jwtInDb != null && !jwtService.isTokenExpired(token)) {
String email = jwtService.extractEmail(token);
log.info("Token is valid. Email extracted: {}", email);
if (jwtInDb.getUserApp() != null && jwtInDb.getUserApp().getEmail().equals(email)
&& SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
log.info("Authenticated user: {}", email);
}
} else {
log.warn("Token is either invalid or expired.");
}
} else {
log.warn("Authorization header missing or does not start with 'Bearer '");
}
filterChain.doFilter(request, response);
}
}
this is my security config
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {
private final BCryptPasswordEncoder passwordEncoder;
private final JwtFilter jwtFilter;
public SpringSecurityConfig(BCryptPasswordEncoder passwordEncoder, JwtFilter jwtFilter) {
this.passwordEncoder = passwordEncoder;
this.jwtFilter = jwtFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf->csrf.disable())
.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth->{
auth.requestMatchers(POST,"/login").permitAll();
auth.requestMatchers(POST, "/logout").authenticated();
auth.anyRequest().authenticated();
})
.logout(logout->{
logout.logoutUrl("/logout").invalidateHttpSession(true).clearAuthentication(true).deleteCookies("JSESSIONID");
})
.sessionManagement(session->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtFilter,UsernamePasswordAuthenticationFilter.class)
.httpBasic(Customizer.withDefaults())
.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService){
DaoAuthenticationProvider daoAuthenticationProvider=new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
return daoAuthenticationProvider;
}
}
finally this is my controller
@Slf4j
@RestController
@RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class utilisateurController {
private UserService userService;
private JwtService jwtService;
private AuthenticationManager authenticationManager;
public utilisateurController(UserService userService, JwtService jwtService, AuthenticationManager authenticationManager) {
this.userService = userService;
this.jwtService = jwtService;
this.authenticationManager = authenticationManager;
}
@PostMapping("/login")
public Map<String, String> login(@RequestBody AuthentificationDTO authentificationDTO) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authentificationDTO.email(), authentificationDTO.password()));
if (authentication.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authentication);
Map<String, String> token = jwtService.generateToken(authentificationDTO.email());
return token;
}
return null;
}
@PostMapping("/logout")
public ResponseEntity<String> logout() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserApp) {
UserApp userApp = (UserApp) authentication.getPrincipal();
log.info("Logging out user: {}", userApp.getEmail());
System.out.printf("Logging out user: %s%n", userApp.getEmail());
SecurityContextHolder.clearContext();
return ResponseEntity.ok("Logged out successfully");
} else {
log.warn("No authenticated user found during logout");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No authenticated user found");
}
}
java lover is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.