2022-02-16

Junit5 - SpringBoot: Error creating bean with name after applied configuration in WebSecurity

Link to the project on Github: https://github.com/sineverba/online-banking-backend/tree/develop

I have these tests for a controller:

@ExtendWith(SpringExtension.class)
@WebMvcTest(BankAccountTransactionsController.class)
class BankAccountTransactionsControllerTest {
    
    @Autowired
    private MockMvc mvc;
    
    @MockBean
    private BankAccountTransactionsService bankAccountTransactionsService;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * index
     */
    @Test
    void testCanIndex() throws Exception {
        var transaction01 = BankAccountTransactionsControllerTest.validBankAccountTransactionsEntity(1L, new BigDecimal(99.99), "First Transaction");
        var transaction02 = BankAccountTransactionsControllerTest.validBankAccountTransactionsEntity(2L, new BigDecimal(150.00), "Second Transaction");
        
        var result = new ArrayList<BankAccountTransactionsEntity>();
        result.add(transaction01);
        result.add(transaction02);
        
        when(bankAccountTransactionsService.index()).thenReturn(result);
        
        mvc.perform(get("/api/v1/bank-account-transactions/"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()", is(2)))
        .andExpect(jsonPath("$[0].id", is(1)))
        .andExpect(jsonPath("$[1].id", is(2)))
        .andExpect(jsonPath("$[0].amount", is(new BigDecimal(99.99))))
        .andExpect(jsonPath("$[1].amount", is(150)))
        .andExpect(jsonPath("$[0].purpose", is("First Transaction")))
        .andExpect(jsonPath("$[1].purpose", is("Second Transaction")))
        .andExpect(jsonPath("$[0]", Matchers.hasKey("transactionDate")))
        .andExpect(jsonPath("$[1]", Matchers.hasKey("transactionDate")));
    }
    
    @Test
    void testCanSave() throws Exception {
        var id = 1L;
        var transactionToSave = validBankAccountTransactionsEntity(new BigDecimal(99.99), "First Transaction");
        var savedTransaction = validBankAccountTransactionsEntity(id, new BigDecimal(99.99), "First Transaction");
        
        when(bankAccountTransactionsService.post(transactionToSave))
        .thenReturn(savedTransaction);
        
        mvc.perform(
                post("/api/v1/bank-account-transactions/")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsBytes(transactionToSave))
                )
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.id", is((int) id)))
        .andExpect(jsonPath("$.amount", is(transactionToSave.getAmount())));
    }
    
    private static BankAccountTransactionsEntity validBankAccountTransactionsEntity(Long id, BigDecimal amount, String purpose) {
        return BankAccountTransactionsEntity.
                builder()
                .id(id)
                .amount(amount)
                .purpose(purpose)
                .build();
    }
    
    private static BankAccountTransactionsEntity validBankAccountTransactionsEntity(BigDecimal amount, String purpose) {
        return BankAccountTransactionsEntity.
                builder()
                .amount(amount)
                .purpose(purpose)
                .build();
    }

It works, and it works very well.

I added a new class:

package com.bitbank.config;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.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 com.bitbank.services.v1.UserDetailsServiceImpl;
import com.bitbank.utils.JwtUtils;
public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = parseJwt(request);
            if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
                String username = jwtUtils.getUserNameFromJwtToken(jwt);
                UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, null);
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {

        }
        filterChain.doFilter(request, response);
    }

    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader("Authorization");
        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
            return headerAuth.substring(7, headerAuth.length());
        }
        return null;
    }
}

And I edited WebSecurityConfig as follow, adding http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    private List<String> getAllowedOrigins() {
        return Arrays.asList("http://localhost:[*]", "https://online-banking-frontend.netlify.app",
                "https://online-banking-frontend.vercel.app");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // Enable CORS and disable CSRF
        http = http.cors().and().csrf().disable();
        http.authorizeRequests().mvcMatchers("/api/v1/ping").permitAll()
                .mvcMatchers("/api/v1/bank-account-transactions").permitAll().anyRequest().authenticated();
        // THIS IS THE ISSUE! =========>
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOriginPatterns(getAllowedOrigins());
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

}

After this, I got error on tests (but not on real launch of Spring)


java.lang.IllegalStateException: Failed to load ApplicationContext

Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authenticationJwtTokenFilter': Unsatisfied dependency expressed through field 'jwtUtils'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.bitbank.utils.JwtUtils' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authenticationJwtTokenFilter': Unsatisfied dependency expressed through field 'jwtUtils'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.bitbank.utils.JwtUtils' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

No qualifying bean of type 'com.bitbank.utils.JwtUtils' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Edit n. 1

I added some MockedBean:

@ExtendWith(SpringExtension.class)
@WebMvcTest(BankAccountTransactionsController.class)
class BankAccountTransactionsControllerTest {
    
    @Autowired
    private MockMvc mvc;
    
    @MockBean
    private BankAccountTransactionsService bankAccountTransactionsService;
    
    // new
    @MockBean
    public AuthTokenFilter authenticationJwtTokenFilter;
    
    // new
    @MockBean
    private JwtUtils jwtUtils;
    
    @Autowired
    private ObjectMapper objectMapper;

And now I have a different issue:

java.lang.IllegalArgumentException: Attribute name must not be null
    at org.springframework.util.Assert.notNull(Assert.java:201)
    at org.springframework.mock.web.MockHttpServletRequest.setAttribute(MockHttpServletRequest.java:762)


from Recent Questions - Stack Overflow https://ift.tt/Zg0Yyo4
https://ift.tt/2seQpNE

No comments:

Post a Comment