Подтвердить что ты не робот

Обрабатывать неавторизованное сообщение об ошибке для базовой проверки подлинности в Spring Безопасность

Я пытаюсь использовать Spring Security 3.0.5 в своем веб-приложении. В принципе, я хочу иметь веб-службу, которая возвращает данные в формате json через HTTP GET.

Я реализовал службу RESTful, которая возвращает данные, когда запрашивается url http://localhost:8080/webapp/json. Это отлично работает со следующей командой curl

> curl http://localhost:8080/webapp/json
{"key":"values"}

После добавления базовой аутентификации с использованием безопасности Spring я могу использовать следующие команды для получения данных

> curl  http://localhost:8080/webapp/json
<html><head><title>Apache Tomcat/6.0.29 - Error report .....
> curl -u username:password http://localhost:8080/webapp/json
{"key":"values"}

Первая команда возвращает стандартную страницу ошибки tomcat, так как теперь она требует имени пользователя и пароля. Мой вопрос в том, можно ли обрабатывать доступ, запрещенный таким образом, что он выводит мое собственное сообщение об ошибке? i.e.

> curl  http://localhost:8080/webapp/json
{"error":"401", "message":"Username and password required"}

Вот моя конфигурация безопасности Spring и AccessDeniedHandler. Как вы можете видеть, я пытаюсь добавить access-denied-handler, который просто выводит строку через ответ сервлета, но он по-прежнему не печатает мое собственное сообщение в командной строке.

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
          http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
    <global-method-security secured-annotations="enabled"/>

    <beans:bean name="access-denied" class="webapp.error.JSONAccessDeniedHandler"></beans:bean>

    <http auto-config="true">
        <access-denied-handler ref="access-denied"/>
        <intercept-url pattern="/json" access="ROLE_USER,ROLE_ADMIN"  />
        <http-basic />
    </http>

    <authentication-manager>
        <authentication-provider>
            <password-encoder hash="md5"/>
            <user-service>
            ...
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>

AccessDeniedHandler.java

package webapp.error;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

public class JSONAccessDeniedHandler implements AccessDeniedHandler  {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        PrintWriter writer = response.getWriter();
        writer.print("{\"error\":\"401\", \"message\":\"Username and password required\"}");
    }

}
4b9b3361

Ответ 1

Я решил свою проблему, поэтому, думаю, я должен поделиться ею здесь. Эта конфигурация позволяет серверу отправлять сообщение об ошибке по-разному в зависимости от запрашивающего программного обеспечения. Если запрос поступает из веб-браузера, он проверяет заголовок User-Agent и перенаправляет на регистрацию формы, если это необходимо. Если запрос исходит, например, curl, он будет распечатывать сообщение об ошибке обычного текста, когда аутентификация завершится с ошибкой.

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:sec="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <!-- AspectJ pointcut expression that locates our "post" method and applies security that way
    <protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>-->
    <sec:global-method-security secured-annotations="enabled"/>

    <bean id="basicAuthenticationFilter"
          class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"
          p:authenticationManager-ref="authenticationManager"
          p:authenticationEntryPoint-ref="basicAuthenticationEntryPoint" />

    <bean id="basicAuthenticationEntryPoint"
          class="webapp.PlainTextBasicAuthenticationEntryPoint"
          p:realmName="myWebapp"/>

    <bean id="formAuthenticationEntryPoint"
          class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"
          p:loginFormUrl="/login.jsp"/>

    <bean id="daep" class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
        <constructor-arg>
            <map>
                <entry key="hasHeader('User-Agent','Mozilla') or hasHeader('User-Agent','Opera') or hasHeader('User-Agent','Explorer')" value-ref="formAuthenticationEntryPoint" />
            </map>
        </constructor-arg>
        <property name="defaultEntryPoint" ref="basicAuthenticationEntryPoint"/>
    </bean>

    <sec:http entry-point-ref="daep">
        <sec:intercept-url pattern="/login.jsp*" filters="none"/>
        <sec:intercept-url pattern="/json" access="ROLE_USER,ROLE_ADMIN"  />
        <sec:intercept-url pattern="/json/*" access="ROLE_USER,ROLE_ADMIN"  />
        <sec:logout
            logout-url="/logout"
            logout-success-url="/home.jsp"/>
        <sec:form-login
            login-page="/login.jsp"
            login-processing-url="/login"
            authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home.jsp"/>
        <sec:custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthenticationFilter" />
    </sec:http>

    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider>
        ...
        </sec:authentication-provider>
    </sec:authentication-manager>

</beans>

PlainTextBasicAuthenticationEntryPoint, расширяя org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

public class PlainTextBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        writer.println("HTTP Status " + HttpServletResponse.SC_UNAUTHORIZED + " - " + authException.getMessage());
    }
}

Ответ 2

Мне интересно, если это когда-нибудь будет сделано для вашего AccessDeniedHandler. Является ли ошибка из-за аутентификации или авторизации? Вызывается ли AccessDeniedHandeler для обоих сценариев? Я сейчас решаю это сам.