Search code examples
javaspring-bootspring-securityjunit

How to implement login service test based on Spring Security in Spring Boot?


I have a problem implementing login service test based on Spring Security in Spring Boot. I tried to implement login method in AuthService and then wrote its service method but I got the issue.

Here is the issue shown below

java.lang.NullPointerException: Cannot invoke "org.springframework.security.core.Authentication.getPrincipal()" because "auth" is null

How can I fix it?

Here is JwtUserDetails class shown below

@RequiredArgsConstructor
public class JwtUserDetails implements UserDetails {

    private final User user;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        UserRole roles = user.getUserRole();

        List<SimpleGrantedAuthority> authories = new ArrayList<>();
        authories.add(new SimpleGrantedAuthority(roles.name()));

        return authories;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public long getId(){
        return user.getId();
    }

    public String getEmail(){
        return user.getEmail();
    }
}

Here is AuthService shown below

@Service
@RequiredArgsConstructor
public class AuthService{

    private final AuthenticationManager authenticationManager;

    private final JwtTokenProvider jwtTokenProvider;

    private final PasswordEncoder passwordEncoder;
   
    private final RefreshTokenService refreshTokenService;

    @Override
    @Transactional
    public AuthResponse login(AdminLoginRequest loginRequest) {

        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
        Authentication auth = authenticationManager.authenticate(authToken);
        SecurityContextHolder.getContext().setAuthentication(auth);
        JwtUserDetails userDetails = (JwtUserDetails) auth.getPrincipal();
        String accessToken = jwtTokenProvider.generateJwtToken(auth);

        List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
                .collect(Collectors.toList());

        RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());

        Date expiryDate = new Date(new Date().getTime() + EXPIRES_IN);

        return AuthResponse.builder()
                .username(userDetails.getUsername())
                .accessToken("Bearer " + accessToken)
                .roles(roles)
                .refreshToken(refreshToken.getToken())
                .message("success")
                .expireDate(expiryDate.getTime())
                .build();
    }

Here is the AuthServiceTest shown below

@ExtendWith(MockitoExtension.class)
class AuthServiceTest{

    @Mock
    private UserRepository userRepository;

    @Mock
    private OrganizationRepository organizationRepository;

    @InjectMocks
    private AuthServiceImpl authService;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private UserMapper userMapper;

    @Mock
    private RefreshTokenService refreshTokenService;

    @Mock
    private JwtTokenProvider jwtTokenProvider;

    @Mock
    private AuthenticationManager authenticationManager;

@Test
    void shouldLogin() {

        // Given
        AdminLoginRequest loginRequest = getLoginRequest();

        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
        Authentication auth = authenticationManager.authenticate(authToken);
        SecurityContextHolder.getContext().setAuthentication(auth);
        JwtUserDetails userDetails = (JwtUserDetails) auth.getPrincipal();

        AuthResponse authResponse = AuthResponse.builder()
                .expireDate(new Date().getTime() + 120000)
                .refreshToken("refreshToken")
                .message("success")
                .accessToken("Bearer access-token")
                .roles(List.of("ADMIN","SUPER_ADMIN"))
                .username("adminUsername")
                .build();

        RefreshToken refreshToken = RefreshToken.builder()
                .token(authResponse.getAccessToken())
                .build();

        // when
        when(authenticationManager.authenticate(any())).thenReturn(authToken);
        when(jwtTokenProvider.generateJwtToken(any())).thenReturn(authResponse.getAccessToken());
        when(refreshTokenService.createRefreshToken(eq(userDetails.getId()))).thenReturn(refreshToken);

        AuthResponse authResponseActual = authService.login(loginRequest);

        // then
        assertThat(authResponse).isNotNull();
        assertEquals(authResponseActual.getAccessToken(), authResponse.getAccessToken());
        assertEquals(authResponseActual.getUsername(), authResponse.getUsername());
        assertEquals(authResponseActual.getRefreshToken(), authResponse.getRefreshToken());
        assertEquals(authResponseActual.getRefreshToken(), authResponse.getRefreshToken());
        assertEquals(authResponseActual.getMessage(), authResponse.getMessage());

    }

Here is the getLoginRequest shown below

public AdminLoginRequest getLoginRequest() {
        return AdminLoginRequest.builder()
                .username("adminUsername")
                .password("adminPassword")
                .build();
    }

Revised

Here is the test method shown below

@Test
    void shouldLogin() {

        // Given
        AdminLoginRequest loginRequest = new UserBuilder().getLoginRequest();


        User user = User.builder()
                .username(loginRequest.getUsername())
                .password(loginRequest.getPassword())
                .userRole(UserRole.ROLE_ADMIN)
                .build();

        user.setId(1L);

        UsernamePasswordAuthenticationToken authToken =
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());

        SecurityContextHolder.getContext().setAuthentication(auth);

        AuthResponse authResponse = AuthResponse.builder()
                .expireDate(new Date().getTime() + 120000)
                .refreshToken("refreshToken")
                .message("success")
                .accessToken("Bearer access-token")
                .roles(List.of("ADMIN","SUPER_ADMIN"))
                .username("adminUsername")
                .build();

        RefreshToken refreshToken = RefreshToken.builder()
                .token(authResponse.getAccessToken())
                .build();

        // when
        when(authenticationManager.authenticate(eq(authToken))).thenReturn(authToken);
        when(auth.getPrincipal()).thenReturn(jwtUserDetails);
        when(jwtTokenProvider.generateJwtToken(eq(auth))).thenReturn(authResponse.getAccessToken());
        when(refreshTokenService.createRefreshToken(eq(jwtUserDetails.getId()))).thenReturn(refreshToken);

        AuthResponse authResponseActual = authService.login(loginRequest);

        // then
        assertThat(authResponse).isNotNull();
        assertEquals(authResponseActual.getAccessToken(), authResponse.getAccessToken());
        assertEquals(authResponseActual.getUsername(), authResponse.getUsername());
        assertEquals(authResponseActual.getRefreshToken(), authResponse.getRefreshToken());
        assertEquals(authResponseActual.getRefreshToken(), authResponse.getRefreshToken());
        assertEquals(authResponseActual.getMessage(), authResponse.getMessage());

    }

Here is the issue shown below

java.lang.ClassCastException: class java.lang.String cannot be cast to class com.ays.backend.user.security.JwtUserDetails (java.lang.String is in module java.base of loader 'bootstrap'; com.ays.backend.user.security.JwtUserDetails is in unnamed module of loader 'app')

Solution

  • Here is the answer shown below

    @Test
        void shouldLogin() {
    
            // Given
            AdminLoginRequest loginRequest = new UserBuilder().getLoginRequest();
    
    
            User user = User.builder()
                    .username(loginRequest.getUsername())
                    .password(loginRequest.getPassword())
                    .userRole(UserRole.ROLE_ADMIN)
                    .build();
    
            user.setId(1L);
    
            UsernamePasswordAuthenticationToken authToken =
                    new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
    
    
            JwtUserDetails jwtUserDetails = new JwtUserDetails(user);
    
            Authentication auth = new UsernamePasswordAuthenticationToken(jwtUserDetails, null);
    
            SecurityContextHolder.getContext().setAuthentication(auth);
    
            JwtUserDetails userDetails = (JwtUserDetails) auth.getPrincipal();
    
    
            AuthResponse authResponse = AuthResponse.builder()
                    .expireDate(new Date().getTime() + 120000)
                    .refreshToken("refreshToken")
                    .message("success")
                    .accessToken("access-token")
                    .roles(List.of("ADMIN","SUPER_ADMIN"))
                    .username("adminUsername")
                    .build();
    
            RefreshToken refreshToken = RefreshToken.builder()
                    .token(authResponse.getRefreshToken())
                    .build();
    
            // when
            when(authenticationManager.authenticate(eq(authToken)))
                    .thenReturn(auth);
            when(jwtTokenProvider.generateJwtToken(eq(auth))).thenReturn(authResponse.getAccessToken());
            when(refreshTokenService.createRefreshToken(eq(userDetails.getId()))).thenReturn(refreshToken);
    
            AuthResponse authResponseActual = authService.login(loginRequest);
    
            // then
            assertThat(authResponse).isNotNull();
            assertEquals(authResponseActual.getAccessToken().substring(7), authResponse.getAccessToken());
            assertEquals(authResponseActual.getUsername(), authResponse.getUsername());
            assertEquals(authResponseActual.getRefreshToken(), authResponse.getRefreshToken());
            assertEquals(authResponseActual.getMessage(), authResponse.getMessage());
    
        }