Comment gérer les différents authentification des exceptions au Printemps de sécurité 3.1?

D'abord je tiens à faire remarquer que j'ai déjà vérifié les autres questions de Débordement de Pile et mis en place ma propre approche basée sur les réponses: https://stackoverflow.com/a/14425801/2487263 et https://stackoverflow.com/a/16101649/2487263

Je suis d'essayer d'obtenir une API REST en utilisant le Printemps de sécurité 3.1 dans un Printemps 3.2, Spring MVC de l'application, j'utilise une authentification de base de l'approche avec une configuration simple:

<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <http-basic />
</http>

Comme vous pouvez le voir, je suis avec mon custom point d'entrée, j'ai ma propre ErrorResponse objet que je vais ajouter à la réponse http au format json, voir le code ci-dessous:

    @Component
public class AuthenticationFailedEntryPoint implements AuthenticationEntryPoint {
    static Logger log = Logger.getLogger(AuthenticationFailedEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.error(ExceptionUtils.getStackTrace(authException));
        ErrorResponse errorResponse = new ErrorResponse();  

            ... here I fill my errorResponse object ...

        ObjectMapper jsonMapper = new ObjectMapper();

        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(status); 

        PrintWriter out = response.getWriter();
        out.print(jsonMapper.writeValueAsString(errorResponse));   
    }
}

J'ai essayé de l'approcher avec deux scénarios de test:

  1. Essayer de consommer un service sans fournir de base en-têtes d'authentification:

C'est la demande/réponse:

GET http://localhost:8081/accounts/accounts?accountNumber=1013


 -- response --
401 Unauthorized
Server:  Apache-Coyote/1.1

Content-Type:  application/json;charset=UTF-8

Content-Length:  320

Date:  Fri, 25 Oct 2013 17:11:15 GMT

Proxy-Connection:  Keep-alive

{"status":401,"messages":[{"code":"000011","message":"You are not authorized to reach this endpoint"}]}

2.- Essayez de consommer le même service, mais maintenant à l'envoi de l'authentification de base les en-têtes avec un mauvais mot de passe:

C'est la demande/réponse:

GET http://localhost:8081/accounts/accounts?accountNumber=1013
Authorization: Basic bXl1c2VyOmdvb2RieWU=


 -- response --
401 Unauthorized
Server:  Apache-Coyote/1.1

WWW-Authenticate:  Basic realm="Spring Security Application"

Content-Type:  text/html;charset=utf-8

Content-Length:  1053

Date:  Fri, 25 Oct 2013 17:03:09 GMT

Proxy-Connection:  Keep-alive

<html> ... ugly html generated by tc server ... </html>

Comme vous pouvez le voir dans le premier cas, le point d'entrée a été atteint et de commencer la méthode a été exécuté avec la bonne tenue de l'exception et de la réponse json a été retourné. Mais qui ne se produisent pas lorsque le mot de passe était un faux.

Dans les journaux, j'ai trouvé que dans les deux cas la production d'un débit différents:

Pour le cas 1 (pas d'auth-têtes):

...
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /accounts?accountNumber=1013; Attributes: [ROLE_USER]
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter@11da1f99, returned: -1
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter@7507ef7, returned: 0
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.web.access.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
...

Pour le cas 2 (mauvais mot de passe):

...
2013-10-25 13:03:08,941 DEBUG tomcat-http--11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Basic Authentication Authorization header found for user 'myuser'
2013-10-25 13:03:08,941 DEBUG tomcat-http--11 org.springframework.security.authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2013-10-25 13:03:09,544 DEBUG tomcat-http--11 org.springframework.security.authentication.dao.DaoAuthenticationProvider - Authentication failed: password does not match stored value
2013-10-25 13:03:09,545 DEBUG tomcat-http--11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-10-25 13:00:30,136 DEBUG tomcat-http--9 org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
...

Le premier cas, jette un AccessDeniedException qui est d'être pris et envoyé à commencer la méthode dans mon point d'entrée, mais le second cas, qui jette un BadCredentialsException ne va pas au point d'entrée.

La chose étrange ici, c'est que le commencent de la méthode est censé recevoir une AuthenticationException, alors, mais AccessDeniedException n'est pas un AuthenticationException mais BadCredentialsException, voir l'arbre d'héritage de la source de sécurité 3.1 documentation de l'API:

java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by org.springframework.security.access.AccessDeniedException
java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by org.springframework.security.core.AuthenticationException
extended by org.springframework.security.authentication.BadCredentialsException

Pourquoi commencer la méthode appelée avec une exception qui n'est pas le type correct et pourquoi pas appelé lorsque la BadCredentialsException qui est du type correct ?

Édité --- mise en œuvre de la réponse par @Luc

Les deux solutions décrites personnalisée AuthenticationEntryPoint indiqué dans la question, la configuration doit être modifiée en sélectionnant l'une des deux options suivantes:

  1. Ajouter une BASIC_AUTH_FILTER:

    <http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <custom-filter position="BASIC_AUTH_FILTER" ref="authenticationFilter" /> 
    </http>
    <beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
    <beans:constructor-arg name="authenticationManager" ref="authenticationManager" />
    <beans:constructor-arg name="authenticationEntryPoint" ref="authenticationFailedEntryPoint" />
    </beans:bean>
  2. OU Ajout d'un point d'entrée pour le http-élément de base, l'OMI est la solution la plus propre:

    <http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <http-basic entry-point-ref="authenticationFailedEntryPoint" />
    </http>
Sur le problème d'exception, a commenté à la fin de la question, il semble que le AccesDeniedException est en cours de traduction par le ExceptionTranlationFilter à un AuthenticationException. C'est pourquoi commencer la méthode de la coutume point d'entrée est appelé
raspacorp - Comment exécuter "commencer" méthode bien que nous ayons @ExceptionalHandler en place?
C'est une question que j'avais presque il y a deux ans et a été résolu en utilisant que la coutume point d'entrée. Je me souviens que les gestionnaires d'exceptions n'étaient pas attraper cette exception, comme il arrive dans une étape précoce. Cependant, je ne sais pas si c'est corrigé dans les versions les plus récentes du cadre, auquel cas, l'ajout d'un bon gestionnaire d'exception fera l'affaire.

OriginalL'auteur raspacorp | 2013-10-25