Как обработать BadCredentialsException используя Spring Security, AngularJS и REST?

156
30 сентября 2017, 19:59

Я работаю над небольшим проектом в целях обучения, который использует Spring Security, AngularJS и REST. При реализации логина столкнулся с такой проблемой:

Если ввожу неверную пару логин/пароль, то мой JsonAuthenticationFilter кидает BadCredentialsException. Соответственно после этого SimpleUrlAuthenticationFailureHandler возвращает 401 http-код. Но сразу затем DispatcherServlet посылает POST запрос по URL /login. Соответствующего метода для обработки этого запроса у меня, конечно, нет, потому что ведь Spring Security обрабатывает все подобные ситуации, а значит в итоге я получаю 405 http-код. Вопрос: почему DispatcherServlet делает это, и как от этого избавиться? В случае с верными логином и паролем все хорошо - могу войти, но в случае ошибки мне нужен мой 401, чтобы работать с ним через Angular...

Логи:

Spring Security config:

<beans:beans xmlns="http://www.springframework.org/schema/security"
         xmlns:beans="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:context="http://www.springframework.org/schema/context"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.dataart.vmishustin.service.security"/>
<http entry-point-ref="restAuthenticationEntryPoint">
    <intercept-url pattern="/resources/**" access="permitAll()"/>
    <intercept-url pattern="/login" access="permitAll()"/>
    <intercept-url pattern="/registration" access="permitAll()"/>
    <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
    <intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')"/>
    <access-denied-handler error-page="/access_denied"/>
    <form-login
                authentication-success-handler-ref="customUrlAuthenticationSuccessHandler"
                authentication-failure-handler-ref="simpleUrlAuthenticationFailureHandler"
                login-page="/login"
                username-parameter="username"
                password-parameter="password"/>
    <csrf disabled="true"/>
    <remember-me remember-me-parameter="rememberMe"/>
    <custom-filter ref="jsonAuthenticationFilter" before="FORM_LOGIN_FILTER"/>
</http>
<authentication-manager id="authenticationManager">
    <authentication-provider user-service-ref="customUserDetailsService">
        <password-encoder ref="passwordEncoder"/>
    </authentication-provider>
</authentication-manager>
<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
    <beans:constructor-arg name="strength" value="11"/>
</beans:bean>
<beans:bean id="jsonAuthenticationFilter" class="com.dataart.vmishustin.service.security.filters.JsonAuthenticationFilter">
    <beans:property name="authenticationManager" ref="authenticationManager"/>
    <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"/>
    <beans:property name="authenticationSuccessHandler" ref="customUrlAuthenticationSuccessHandler"/>
</beans:bean>
<beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>

RestAuthenticationEntryPoint

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
      response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
  }
}

JsonAuthenticationFilter

@Component
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
  private static final Logger LOG = LoggerFactory.getLogger(JsonAuthenticationFilter.class);
  private static final String JSON_CONTENT_TYPE = "application/json;charset=UTF-8";
  private static final String REQUIRED_METHOD = "POST";
  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (!request.getMethod().equals(REQUIRED_METHOD)) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    if (JSON_CONTENT_TYPE.equals(request.getHeader("Content-Type"))) {
        LoginRequest loginRequest = this.getLoginRequest(request);
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    } else {
        return super.attemptAuthentication(request, response);
    }
  }
  private LoginRequest getLoginRequest(HttpServletRequest request) {
    LoginRequest loginRequest = null;
    try (BufferedReader reader = request.getReader()) {
        Gson gson = new Gson();
        loginRequest = gson.fromJson(reader, LoginRequest.class);
    } catch (IOException e) {
        LOG.error("IOException has been thrown", e);
    }
    if (loginRequest == null) {
        loginRequest = new LoginRequest();
    }
    return loginRequest;
  }
}

Если потребуются еще какие-то классы или конфигурация, то я обязательно добавлю, но прямо сейчас даже представить не могу, где может быть проблема. Буду рад любой помощи. Спасибо

READ ALSO
Правильное регулярные выражение

Правильное регулярные выражение

Представим, что есть строки: 'Привет, у тебя есть чит?', 'Привет, у тебя есть чит', 'Привет, пойдём сегодня читать?'

182
Kартинка не обновляется jsf

Kартинка не обновляется jsf

Извлекаю из базы данных строки, изображение, дату помещаю значения в поля ввода изменяю и произвожу "апдейт",- работает все кроме изображения!...

223
Как создать файловый поток в Xamarin(C#)?

Как создать файловый поток в Xamarin(C#)?

Нужно открыть поток для чтения бинарного файла, но выдаёт ошибкуЯ делал десктопное приложение, а нужно на мобильное

261
Мультиязычность ASP.NET MVC

Мультиязычность ASP.NET MVC

Как лучше сделать многоязычность в приложении? На данный момент использую файлы ресурсов и там перевожу нужные мне слова на разные языки,...

232