5

I wrote an application as part of learning Spring, but when I test authentication I receive 401 status instead of 200. I was looking for the cause of the error and it seems to me that the line Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password)); returns null. However, I do not know how to solve this problem.

@Component
public class AuthenticationServiceUsernamePassword {
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationServiceUsernamePassword.class);
    @Autowired
    @Qualifier("customAuthenticationManager")
    private AuthenticationManager authenticationManager;
    @Autowired
    private TokenManager tokenManager;

    public SignedJWT authenticate(final String email, final String password){
        try {
            Authentication authentication = authenticationManager
                .authenticate(new UsernamePasswordAuthenticationToken(email, password));        
            SecurityContextHolder.getContext()
                .setAuthentication(authentication);

            if (authentication.getPrincipal() != null) {
                return tokenManager.createNewToken((PrincipalUser) authentication.getPrincipal());
            }
        } catch (AuthenticationException authException) {
            LOGGER.debug("Authentication failed for user:\"" + email + ".\" Reason " + authException.getClass());
        }

        return null;
    }
}

Controller

@Controller
public class AuthController {
    @Value("${jwt.result}")
    private String defaultTokenResponse;
    @Autowired
    private AuthenticationServiceUsernamePassword authUserPassword;

    @RequestMapping(value = "/authentication", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> authenticate(String email, String password, HttpServletRequest request,
                                           HttpServletResponse response){
        if (email != null && password != null){
            try {
                SignedJWT token = authUserPassword.authenticate(email, password);

                if (token != null){
                    return new ResponseEntity<String>(String.format(defaultTokenResponse, token.serialize()),
                        HttpStatus.OK);
                } else {
                    return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
                }
            } catch (BadCredentialsException badCredentials) {
                return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
            }
        } else {
            return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
        }
    }
}

Test class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class ConnectControllerTest {
    protected MockMvc mockMvc;
    @Autowired
    private WebApplicationContext context;
    @Autowired
    private Filter springSecurityFilterChain;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .addFilters(springSecurityFilterChain)
            .defaultRequest(get("/"))
            .build();
    }

    @Test
    public void shouldTestAuthentication() throws Exception {
        String result = mockMvc.perform(post("/authentication")
            .param("email", "user@test.pl").param("password", "password"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.token").exists())
            .andReturn().getResponse().getContentAsString();
    }
}

If anyone would be interested in the rest of the code here is the link: repository

mariusz
  • 157
  • 2
  • 16

3 Answers3

2

Ok. First thing first

Email and Password are passed correctly

Problem is here

public SignedJWT authenticate(final String email, final String password){
        try {
            System.out.println("test => "+email+" : "+password);
            Authentication authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(email, password));
            SecurityContextHolder.getContext().setAuthentication(authentication);

            if (authentication.getPrincipal() != null) {
                return tokenManager.createNewToken((PrincipalUser) authentication.getPrincipal());
            }
        } catch (AuthenticationException authException) {
            authException.printStackTrace();
            LOGGER.debug("Authentication failed for user:\"" + email + ".\" Reason " + authException.getClass());
        }
        System.out.println("return nulll");
        return null;
    }

If you run your test case it will throw following error

org.springframework.security.authentication.BadCredentialsException: Bad credentials
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:98)
    at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:504)
    at com.github.springjwt.security.jwt.service.AuthenticationServiceUsernamePassword.authenticate(AuthenticationServiceUsernamePassword.java:30)
    at com.github.springjwt.web.api.controller.AuthController.authenticate(AuthController.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImp

Which means your testcase's username and password didnt match with UserRepository class User detail

In your UserRepository class you need to set a correct hashed password and its salt value which you have set to null.

When you call authenticate.authenticate it internally gets password and hash and matched it with passed value.

If values doesn't match it throws Bad credentials error

P.S : I came to this conclusion after running your code locally

MyTwoCents
  • 7,284
  • 3
  • 24
  • 52
  • What if I changed current `UserRepository` to `public interface UserRepository extends JpaRepository`? Do I still have to set up hashed password in this situation? – mariusz Oct 18 '18 at 11:37
  • yes you can. Bottomline is password passed and hashed password should match after encoding using salt logic. ELse you will get 404. – MyTwoCents Oct 18 '18 at 11:40
  • I changed my `UserRepository` and created my test User with matching data but still get 401. I probably do something wrong, but I Can't figure out what. All the changes I made are visible in the repository. – mariusz Oct 19 '18 at 05:58
1

Your code is mostly correct, it goes wrong in your controller definition:

public ResponseEntity<String> authenticate(String email, String password, HttpServletRequest request,
                                           HttpServletResponse response){

Spring does not know how to retrieve the email and password variables by default. You need to annotate these with the @RequestBody annotation, like:

public ResponseEntity<String> authenticate(@RequestBody String email, @RequestBody String password, HttpServletRequest request,
                                           HttpServletResponse response){

However if your whole controller will serve as an API you can also annotate your controller with @RestController which tells spring to use the @RequestBody for every parameter and that every method should be annotated with @ResponseBody which will tell spring that the return values should be converted to JSON (which is convenient for an API).

References:

Spring’s RequestBody and ResponseBody Annotations

Sven Hakvoort
  • 3,543
  • 2
  • 17
  • 34
0

You have added params to you unit test case. Not payload. If you add payload then you have to use @RequestBody. Here you have to use @RequestParam. Use below code in controller :

public ResponseEntity<String> authenticate(@RequestParam String email, @RequestParam String password, HttpServletRequest request,HttpServletResponse response){
 ..do stuff
}

This will work ..!

Vishw Patel
  • 529
  • 2
  • 9