I am trying to implement jwt in my spring boot project but have this error whenever I tested the token in postman, I don’t know what I did wrong in the implementation. The thing is that when I register a new user or login as an existing user, the app is able to return a token to me but when I used the returned token to perform other validation with the “Bearer ” keyword, I got this error that says Compact JWT strings may not contain whitespace. Here is the logcat and my code.
io.jsonwebtoken.MalformedJwtException: Compact JWT strings may not contain whitespace.
at io.jsonwebtoken.impl.JwtTokenizer.tokenize(JwtTokenizer.java:67) ~[jjwt-impl-0.12.3.jar:0.12.3]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:370) ~[jjwt-impl-0.12.3.jar:0.12.3]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:362) ~[jjwt-impl-0.12.3.jar:0.12.3]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:94) ~[jjwt-impl-0.12.3.jar:0.12.3]
at io.jsonwebtoken.impl.io.AbstractParser.parse(AbstractParser.java:36) ~[jjwt-impl-0.12.3.jar:0.12.3]
at io.jsonwebtoken.impl.io.AbstractParser.parse(AbstractParser.java:29) ~[jjwt-impl-0.12.3.jar:0.12.3]
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJwt(DefaultJwtParser.java:787) ~[jjwt-impl-0.12.3.jar:0.12.3]
at com.taskmanagementsystem.TaskManagementSystem.security.service.UserService.extractAllClaims(UserService.java:55) ~[classes/:na]
at com.taskmanagementsystem.TaskManagementSystem.security.service.UserService.extractClaims(UserService.java:47) ~[classes/:na]
at com.taskmanagementsystem.TaskManagementSystem.security.service.UserService.extractUsername(UserService.java:24) ~[classes/:na]
at com.taskmanagementsystem.TaskManagementSystem.security.config.AuthenticationFilterConfig.doFilterInternal(AuthenticationFilterConfig.java:39) ~[classes/:na]
@Service
public class UserService {
@Value("${secretKey}")
private String secretKey;
public String extractUsername(String token){
return extractClaims(token,Claims::getSubject);
}
public boolean isTokenValid(String token, UserDetails userDetails){
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())&& !isTokenExpired(token));
}
private Date extractExpiration(String token){
return extractClaims(token,Claims::getExpiration);
}
public boolean isTokenExpired(String token){
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails){
return Jwts.builder()
.setSubject(userDetails.getUsername())
.claim("authorities",populateAuthorities(userDetails.getAuthorities()))
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis()+1000 *60 *24))
.signWith(getSignInkey(), SignatureAlgorithm.HS256)
.compact();
}
public <T> T extractClaims (String token, Function<Claims,T> claimsTFunction){
final Claims claims = extractAllClaims(token);
return claimsTFunction.apply(claims);
}
private Claims extractAllClaims(String token){
return Jwts
.parser()
.setSigningKey(getSignInkey())
.build()
.parseClaimsJwt(token)
.getBody();
}
private Key getSignInkey(){
byte [] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
private String populateAuthorities(Collection<? extends GrantedAuthority>authorities){
Set<String> authoritySet = new HashSet<>();
for (GrantedAuthority authority: authorities){
authoritySet.add(authority.getAuthority());
}
return String.join(",",authoritySet);
}
}
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {
private final AuthenticationFilterConfig authenticationFilterConfig;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(request ->
request.requestMatchers("/api/auth/**","/swagger-ui/**")
.permitAll()
.requestMatchers("/task/create").hasAnyRole(ADMIN.name(), USER.name())
.anyRequest()
.authenticated())
.sessionManagement(session ->session.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(authenticationFilterConfig, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@Component
@RequiredArgsConstructor
public class AuthenticationFilterConfig extends OncePerRequestFilter {
private final UserService userService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
if (authorizationHeader ==null || !authorizationHeader.startsWith("Bearer ")){
filterChain.doFilter(request,response);
return;
}
jwt = authorizationHeader.substring(7);
userEmail = userService.extractUsername(jwt);
if (userEmail !=null && SecurityContextHolder.getContext().getAuthentication()==null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
if (userService.isTokenValid(jwt,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request,response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return request.getContextPath().contains("/api/auth");
}
}
@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
private final UserRepository userRepository;
@Bean
public UserDetailsService userDetailsService(){
return username -> userRepository.findByEmail(username)
.orElseThrow(()-> new UsernameNotFoundException("User not found"));
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}