I have a user with role “ROLE_USER”, i can signin this user with no problem and signin endpoint generates a JWT each time i hit it with n o problem. I’m trying to trigger an endpoint with signedin user with jwt and role-based authentication. When i try to trigger the endpoint below, i get these two errors:
d.s.s.security.jwt.JwtUtils : Invalid JWT signature: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
d.s.s.security.jwt.AuthEntryPointJwt: Unauthorized error: Full
authentication is required to access this resource
Here is the related code below:
AuthController.java:
package dev.spring.spring_jwt_auth.controller;
import dev.spring.spring_jwt_auth.model.EnumRole;
import dev.spring.spring_jwt_auth.model.Role;
import dev.spring.spring_jwt_auth.model.User;
import dev.spring.spring_jwt_auth.payload.request.LoginRequest;
import dev.spring.spring_jwt_auth.payload.request.SignupRequest;
import dev.spring.spring_jwt_auth.payload.response.JwtResponse;
import dev.spring.spring_jwt_auth.repository.RoleRepository;
import dev.spring.spring_jwt_auth.repository.UserRepository;
import dev.spring.spring_jwt_auth.security.jwt.JwtUtils;
import dev.spring.spring_jwt_auth.security.services.RefreshTokenService;
import dev.spring.spring_jwt_auth.security.services.UserDetailsImpl;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private AuthenticationManager authenticationManager;
private UserRepository userRepository;
private RoleRepository roleRepository;
private JwtUtils jwtUtils;
private PasswordEncoder passwordEncoder;
private RefreshTokenService refreshTokenService;
@Autowired
public AuthController(AuthenticationManager authenticationManager,
UserRepository userRepository,
PasswordEncoder passwordEncoder,
RoleRepository roleRepository,
JwtUtils jwtUtils,
RefreshTokenService refreshTokenService) {
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.roleRepository = roleRepository;
this.jwtUtils = jwtUtils;
this.refreshTokenService = refreshTokenService;
}
public AuthController() {
}
@PostMapping("/signin")
public ResponseEntity<?> signin(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = jwtUtils.generateToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
return ResponseEntity.ok(new JwtResponse(token, userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles));
}
@PostMapping("/signup")
public ResponseEntity<?> signup(@Valid @RequestBody SignupRequest signupRequest) {
if (userRepository.existsByUsername(signupRequest.getUsername())) {
return ResponseEntity.badRequest().body("Username is already taken");
}
if (userRepository.existsByEmail(signupRequest.getEmail())) {
return ResponseEntity.badRequest().body("Email is already taken");
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode(signupRequest.getPassword());
User user = new User(signupRequest.getUsername(), signupRequest.getEmail(), encodedPassword);
Set<Role> userRoles = signupRequest.getRoles();
Set<Role> roles = new HashSet<>();
if (userRoles == null) {
Role role = roleRepository.findByName("ROLE_USER").orElseThrow(() -> new RuntimeException("Role is not found"));
roles.add(role);
} else {
userRoles.forEach(role -> {
if (role.getName().equals("ROLE_ADMIN")) {
Role role1 = roleRepository.findByName("ROLE_ADMIN").orElseThrow(() -> new RuntimeException("Role is not found"));
roles.add(role1);
} else if (role.getName().equals("ROLE_MODERATOR")) {
Role role1 = roleRepository.findByName("ROLE_MODERATOR").orElseThrow(() -> new RuntimeException("Role is not found"));
roles.add(role1);
} else {
Role role1 = roleRepository.findByName("ROLE_USER").orElseThrow(() -> new RuntimeException("Role is not found"));
roles.add(role1);
}
});
}
user.setRoles(roles);
userRepository.saveAndFlush(user);
return ResponseEntity.ok(user);
}
}
TestController.java
package dev.spring.spring_jwt_auth.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/test")
public class TestController {
@GetMapping("/all")
public String all() {
return "public content";
}
@GetMapping("/user")
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_MODERATOR') or hasRole('ROLE_ADMIN')")
public String user() {
return "user content";
}
@GetMapping("/moderator")
@PreAuthorize("hasRole('ROLE_MODERATOR') or hasRole('ROLE_ADMIN')")
public String mod() {
return "moderator content";
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String admin() {
return "admin content";
}
}
AuthEntryPointJwt.java:
package dev.spring.spring_jwt_auth.security.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
HashMap<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}
WebSecurityConfig.java:
package dev.spring.spring_jwt_auth.security;
import dev.spring.spring_jwt_auth.security.jwt.AuthEntryPointJwt;
import dev.spring.spring_jwt_auth.security.jwt.AuthTokenFilter;
import dev.spring.spring_jwt_auth.security.services.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableMethodSecurity
public class WebSecurityConfig {
private UserDetailsServiceImpl userDetailsService;
private AuthEntryPointJwt entryPointJwt;
@Autowired
public WebSecurityConfig(UserDetailsServiceImpl userDetailsService, AuthEntryPointJwt entryPointJwt) {
this.userDetailsService = userDetailsService;
this.entryPointJwt = entryPointJwt;
}
@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(e -> e.authenticationEntryPoint(entryPointJwt))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").authenticated());
http.authenticationProvider(daoAuthenticationProvider());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
AuthTokenFilter.java
package dev.spring.spring_jwt_auth.security.jwt;
import dev.spring.spring_jwt_auth.security.services.UserDetailsServiceImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class AuthTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = parseJwt(request);
try {
if (jwt != null && jwtUtils.validateToken(jwt)) {
String username = jwtUtils.generateUsernameFromToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,
null,
userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (Exception e) {
logger.error("Cannot set user authentication {}", e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (StringUtils.hasText(header) && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}
JwtUtils.java:
package dev.spring.spring_jwt_auth.security.jwt;
import dev.spring.spring_jwt_auth.security.services.UserDetailsImpl;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtUtils {
private final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
@Value("${app.secretKey}")
private String secretKey;
@Value("${app.jwtExpirationTime}")
private int jwtExpirationTime;
public String generateToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.subject((userPrincipal.getUsername()))
.issuedAt(new Date())
.expiration(new Date((new Date()).getTime() + jwtExpirationTime))
.signWith(Jwts.SIG.HS512.key().build(), Jwts.SIG.HS512)
.compact();
}
public SecretKey key() {
return Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
}
public String generateUsernameFromToken(String token) {
return Jwts
.parser()
.verifyWith(Jwts.SIG.HS512.key().build())
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts
.parser()
.verifyWith(Jwts.SIG.HS512.key().build())
.build()
.parseSignedClaims(token);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
Any help is appreciated!
7