Search code examples
springspring-bootspring-mvcspring-boot-security

Upgrading Spring-Security to latest(6.1.0)


I am trying to upgrade my old code(2 years old) to a newer project that is currently holding the latest version(Spring boot security V6.1.0) due to a serious vulnerability in its artifacts.

I'm trying to keep the same structure, but I am facing some difficulties with the Spring boot security framework.

In general, in each request, I am extracting a JWT token from the Authorization header, and running some logic to determine if the user is authorized or not.

This is my old code:

@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticateServiceImpl authenticateService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(authenticateService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/users/createUser").permitAll()//todo fix in the future
                .antMatchers("/users/getProfile").permitAll()//todo fix in the future
                .antMatchers("/users/**").hasRole( "ADMIN")
                .antMatchers("/contacts/**").hasRole( "ADMIN")
                .anyRequest().authenticated()
                .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

This is my new code for Spring-Security V6.1.0 which is currently making an issue with authenticationManagerBean because the new class is not extending from WebSecurityConfigurerAdapter(which is not exist any more).

@Configuration
@EnableWebSecurity
public class SecurityConfigurer{

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers("/auth/**").permitAll()
                        .requestMatchers("/users/createUser").permitAll()//todo fix in the future
                        .requestMatchers("/users/getProfile").permitAll()//todo fix in the future
                        .requestMatchers("/users/**").hasRole("ADMIN")
                        .requestMatchers("/contacts/**").hasRole("ADMIN")
                        .anyRequest().authenticated()
                );

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }


    /*@Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }*/

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

How can I implement AuthenticationManager class, or finding an alternative for that because I am using this on my Auth services like this:

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private AuthenticateServiceImpl authenticateService;

    @Autowired
    private Definitions definitions;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserServiceImpl userService;

    @PostMapping(value = "/login")
    public ResponseEntity<?> login(@RequestBody AuthenticationRequest authenticationRequest) throws UnauthorizedException {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new UnauthorizedException(definitions.INVALID_CREDENTIAL);
        }
        final UserDetails userDetails = authenticateService
                .loadUserByUsername(authenticationRequest.getUsername());

        User user = userRepository.findUserByUsername(authenticationRequest.getUsername());
        return ResponseEntity.ok()
                .body(new AuthenticationResponse(jwtUtil.generateToken(userDetails), userService.convertToDtoRestricted(user)));
    }
}

Just in case it's needed I am attaching the class JwtRequestFilter :

@Component
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter {
    @Autowired
    private AuthenticateServiceImpl authenticateService;

    @Autowired
    private JwtUtil jwtUtil;



    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;

        if (authorizationHeader != null) {
            if (authorizationHeader.startsWith("Bearer ")) {
                jwt = authorizationHeader.substring(7);
                try {
                    username = jwtUtil.extractUsername(jwt);
                } catch (SignatureException e) {
                    if(log.isInfoEnabled()) {
                        log.info("~JWT: Invalid Token ~ Access Denied!");
                    }
                } catch (ExpiredJwtException e) {
                    if(log.isInfoEnabled()) {
                        log.info("~JWT: Expired Token ~ Access Denied!");
                    }
                }
            }
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.authenticateService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }

}

Thanks for your help and time!


Solution

  • in your case you should create a

    Global AuthenticationManager

    In SecurityConfigurer you just will have following bean declaration:

    @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();}
    

    And it will work great

    P.S. Give me a feedback if it was helpfuly