When I request APIs in POSTMAN protected by Spring Boot Security, I get the following error: Unauthorized error: Full authentication is required to access this resource . But in Swagger, things are going well. These are my code in the following:
@PostMapping(value = "/save")
@Operation(summary = "save", security = @SecurityRequirement(name = "bearerAuth"))
public HttpStatus saveSeans(@RequestBody NewSeansRequestDto dto) {
Seans seans = seansMapper.map(dto);
seansRepository.save(seans);
return HttpStatus.CREATED;
}
This my security configuration:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationEntryPoint authenticationEntryPoint;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeHttpRequests()
.requestMatchers("/api/v1/auth/**",
"/helpers/**",
"/roles/**",
"/films/**",
"/seans/**",
"/v2/api-docs",
"/v3/api-docs",
"/v3/api-docs/**",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui/**",
"/webjars/**",
"/swagger-ui.html")
.permitAll()
.anyRequest().authenticated();
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// get JWT (token) from http request
String token = getJWTFromRequest(request);
// validate token
if (StringUtils.hasText(token) && jwtUtil.validateJwtToken(token)) {
// get username from token
String username = jwtUtil.getUsernameFromJwtToken(token);
// load user associated with token
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// set spring security
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
private String getJWTFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
@Component
public class JwtUtil {
private static final Logger LOG = LogManager.getLogger(JwtUtil.class);
@Value("${app.jwt-secret}")
private String jwtSecretKey;
private JwtParser createParser() {
return Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(jwtSecretKey.getBytes())).build();
}
//get username from the Token
public String getUsernameFromJwtToken(String token) {
Claims claims = createParser().parseClaimsJws(token).getBody();
return claims.getSubject();
}
//validate JWT token
public boolean validateJwtToken(String authToken) {
try {
createParser().parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
LOG.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
LOG.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
LOG.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
LOG.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
LOG.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Transactional
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return new UserPrincipal(userRepository.findByEmail(email).orElseThrow(EmailNotFoundException::new));
}
}
public class UserPrincipal implements UserDetails {
private User user;
public UserPrincipal(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return mapToGrantedAuthority(user.getRoles());
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
private Set<GrantedAuthority> mapToGrantedAuthority(Set<Role> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toSet());
}
}
each api that protected by spring boot security comes in the following class.
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<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);
}
}
I can't understand where I'm missing, everything has to work correctly. Because the front end programmer should not get a 401 error.