Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring returns 401 instead of 200 status

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", "[email protected]").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

like image 797
mariusz Avatar asked Oct 17 '22 11:10

mariusz


2 Answers

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

like image 110
MyTwoCents Avatar answered Oct 21 '22 10:10

MyTwoCents


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

like image 22
Sven Hakvoort Avatar answered Oct 21 '22 09:10

Sven Hakvoort