3

I am trying to test my Spring Application which consists of JWT auth with Junit and Mockmvc. Signup test is working as expected. But when trying to test login, StackOverflowError is displayed. Inside the JWTAuthenticationFilter.java, data is being successfully received. But after that error is displayed. Please help me out. Thanks!

Error:


java.lang.StackOverflowError
    at org.mockito.internal.invocation.MatchersBinder.bindMatchers(MatchersBinder.java:25)
    at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:59)
    at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
    at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:35)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:63)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:49)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:110)
    at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder$MockitoMock$310380589.encode(Unknown Source)
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.prepareTimingAttackProtection(DaoAuthenticationProvider.java:142)
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:106)
    at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200)
    at org.springframework.security.con

WebSecurity.java


@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private UserDetailsServiceImpl userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;


    public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // this disables session creation on Spring Security
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

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

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.parentAuthenticationManager(authenticationManagerBean())
                .userDetailsService(userDetailsService)
                .passwordEncoder(bCryptPasswordEncoder);
    }

}

UserControllerTest.java


@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
public class UserControllerTest {
    @Autowired
    private MockMvc mvc;

    @Autowired
    private JacksonTester<User> json;

    @Autowired
    private JacksonTester<CreateUserRequest> jsonN;

    @MockBean
    private UserRepository userRepository;

    @MockBean
    private BCryptPasswordEncoder encoder;

    private  CreateUserRequest r;

    @Before
    public void setup(){
        r = new CreateUserRequest();
        r.setUsername("ujjwal2102");
        r.setPassword("ujjwal21");
        r.setConfirmPassword("ujjwal21");


    }

    @Test
    public void createUserTest() throws Exception{
        signup();
    }

    @Test
    public void loginUserTest() throws Exception{
        signup();
        login();
    }

    public void signup() throws Exception{
        when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");

        mvc.perform(
                post(new URI("/api/user/create"))
                        .content(jsonN.write(r).getJson())
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .accept(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id",is(0)))
                .andExpect(jsonPath("$.username",is("ujjwal2102")));
    }

    public void login() throws Exception{

        User user = new User();
        user.setUsername("ujjwal2102");
        user.setPassword("ujjwal21");

        when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
        mvc.perform(
                    post(new URI("/login"))
                        .content(json.write(user).getJson())
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .accept(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk());



    }


}

User.java


@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonProperty
    private long id;
    
    @Column(nullable = false, unique = true)
    @JsonProperty
    private String username;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Column(nullable = false)
    private String password;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "cart_id", referencedColumnName = "id")
    @JsonIgnore
    private Cart cart;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public Cart getCart() {
        return cart;
    }

    public void setCart(Cart cart) {
        this.cart = cart;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
    
    
    
}

JWTAuthenticationFilter.java


public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            User creds = new ObjectMapper()
                    .readValue(req.getInputStream(), User.class);
            System.out.println("USERNAME-----" + creds.getUsername());
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {

        String token = JWT.create()
                .withSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
                .sign(HMAC512(SecurityConstants.SECRET.getBytes()));
        System.out.println("TOKEN----" + token);
        res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
    }
}
Ujjwal Mishra
  • 177
  • 1
  • 8
  • something is calling itself recursively. btw the log you've posted is not complete, would you mind posting the full log – mangusta Aug 08 '20 at 16:49

1 Answers1

0

I noticed one mistake that may or may not solved your issue, but may compromise your authentification process.

new UsernamePasswordAuthenticationToken(
    creds.getUsername(), 
    creds.getPassword(), 
    new ArrayList<>()) // with autorities, the user is authenticated

new UsernamePasswordAuthenticationToken(
    creds.getUsername(), 
    creds.getPassword()) // without autorities, the user is not authenticated

The constructor with autorities is usually used by the AuthentificationManager after authentification is successful. The constructor without is used by the filter to pass to the AuthentificationManager.

Spring Security Javadoc

Looking at the log, the issue looks to be in your Spring Security Configuration that you didn't provide. You may have done a recursive call inside an authentification provider.

Cyril G.
  • 1,879
  • 2
  • 5
  • 19