0

When I am trying to authenticate an user from AngularJS, I am seeing this warning in Spring Boot log:

[WARN ] 2017-02-04 17:09:20.085 [http-nio-8080-exec-1] DefaultHandlerExceptionResolver - Resolved exception caused by Handler execution: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'null' not supported

And the browser response is:

415 Unsupported Media Type

My LoginController:

@RestController
// @RequestMapping("/")
public class LoginController {

    public Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping(value = "/login", method = RequestMethod.GET, 
        consumes = MediaType.APPLICATION_JSON_VALUE 
        /*produces = MediaType.APPLICATION_JSON_VALUE*/)
    public ResponseEntity<Admin> login(@RequestBody UserDTO user, BindingResult result, WebRequest request) {
        logger.info("********** Inside login of LoginController **************");

        Admin authenticatedUser = (Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        HttpStatus httpStatus = null;
        if (authenticatedUser == null) {
            httpStatus = HttpStatus.NOT_FOUND;
        } else {
            httpStatus = HttpStatus.OK;
        }
        return new ResponseEntity<Admin>(authenticatedUser, httpStatus);
    }
}

My AngularJS code:

service.login = function(user, successHandler, errorHandler) {

// Obtain a CSRF token
 loginResources.options().$promise.then(function (response) {
  console.log('Obtained a CSRF token in a cookie', response);

  // Extract the CSRF token
  var csrfToken = Cookies.getFromDocument($http.defaults.xsrfCookieName);
  console.log('Extracted the CSRF token from the cookie', csrfToken);

  // Prepare the headers
  var headers = {
    'Content-Type': 'application/json'
  };
  headers[$http.defaults.xsrfHeaderName] = csrfToken;
  console.log("Before calling /login, user : ", user);
  // Post the credentials for logging in
  $http.get(ApiBasePath + '/login', user, {headers: headers})
    .success(successHandler)
    .error(function (data, status, headers, config) {

      if (isCSRFTokenInvalidOrMissing(data, status)) {
        console.error('The obtained CSRF token was either missing or invalid. Have you turned on your cookies?');

      } else {
        // Nope, the error is due to something else. Run the error handler...
        errorHandler(data, status, headers, config);
      }
    });

}).catch(function(response) {
  console.error('Could not contact the server... is it online? Are we?', response);
});

};//login function ends

I have an exactly same registration controller with an exactly same AngularJS register function (with different endpoint of course), but that works perfectly.

I doubt one thing though, when I am using Spring Security, do I really need the LoginController with the endpoint /login or the security configuration will take care of that? My security config:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/*/**").permitAll()
            .antMatchers("/login").permitAll()
            .antMatchers("/register").permitAll()
            .antMatchers("/", "/**/*.css", "/**/**/*,css",
                "/**/*.js", "/**/**/*.js").permitAll()
            .antMatchers("/dashboard", "/dasboard/**", "/logout").authenticated();

    // Handlers and entry points
    http
         .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
    http
        .formLogin()
           .successHandler(authenticationSuccessHandler);
    http
        .formLogin()
           .failureHandler(authenticationFailureHandler);

    // Logout
    http
        .logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler(logoutSuccessHandler);

    // CORS
    http
        .addFilterBefore(corsFilter, ChannelProcessingFilter.class);

    // CSRF
    http
        .csrf().requireCsrfProtectionMatcher(
                new AndRequestMatcher(
                    // Apply CSRF protection to all paths that do NOT match the ones below
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.OPTIONS.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.GET.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.POST.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.HEAD.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.TRACE.toString())),

                    new NegatedRequestMatcher(new AntPathRequestMatcher("/css/**", HttpMethod.GET.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/js/**", HttpMethod.GET.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/js/**/**", HttpMethod.GET.toString())),
                    // We disable CSRF at login/logout, but only for OPTIONS methods
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/login*/**", HttpMethod.OPTIONS.toString())),
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/logout*/**", HttpMethod.OPTIONS.toString())),

                    //Disable CSRF at register for all methods
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/register*/**", HttpMethod.OPTIONS.toString()))
                )
            );
    http
        .addFilterAfter(new CsrfTokenResponseCookieBindingFilter(), CsrfFilter.class); // CSRF tokens handling
}

@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
    auth.authenticationProvider(authProvider());
}

@Bean
public DaoAuthenticationProvider authProvider() {
    final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(encoder());
    return authProvider;
}

@Bean
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder(11);
}
dur
  • 15,689
  • 25
  • 79
  • 125
Imran K
  • 81
  • 1
  • 12
  • Uncommenting that doesn't make any difference. – Imran K Feb 04 '17 at 15:59
  • But where do check this? As I think, the call to "/login" is handled by the SecurityConfig class and not by the LoginController. Also, do you have any thought on this: "I doubt one thing though, when I am using Spring Security, do I really need the LoginController with the endpoint /login or the security configuration will take care of that?" @dur – Imran K Feb 06 '17 at 05:59
  • Could this be a solution: [link] http://stackoverflow.com/questions/11492325/post-json-fails-with-415-unsupported-media-type-spring-3-mvc[/link]? I am going to try it out. – Imran K Feb 06 '17 at 06:53
  • I don't think so. I have examples in spring.io and other places where they called the angularjs authenticate function with http GET. – Imran K Feb 06 '17 at 10:58
  • I don't think there is any connection with form and post, as I am collecting the form data in angular and making an http call. Anyway, I tried with POST too. In that case, my username and password is not flowing till the `LoginService` which is an custom implementation of `UserDetailsService`. I see the username is null inside `loadUserByUserName` – Imran K Feb 06 '17 at 17:11
  • I am sending the whole user object from angular. – Imran K Feb 06 '17 at 17:19

1 Answers1

0

Finally I got the answer. It is true, if I try to send json object instead of request parameters, that I have to use Custom UserNamePasswordAuthenticationFilter. It is also true, I have to use POST.

Thanks @dur for pointing that.

Finally, a big thanks to this post. Without this post, I wouldn't have possibly find out how to customize the filter.

Community
  • 1
  • 1
Imran K
  • 81
  • 1
  • 12