Prima di tutto voglio commentare che ho già controllato le altre domande di Stack Overflow e realizzato il mio approccio sulla base delle risposte: https://stackoverflow.com/a/14425801/2487263 e https://stackoverflow.com/a/16101649/2487263Come gestire le diverse eccezioni di autenticazione in Spring Security 3.1?
Sto cercando di assicurarsi un API REST utilizzando la protezione Primavera 3.1 in un Primavera 3.2, l'applicazione Spring MVC, sto usando un approccio di base di autenticazione con una semplice configurazione:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER"/>
<http-basic />
</http>
Come potete vedere sto usando il mio punto di ingresso personalizzato ho il mio oggetto ErrorResponse che aggiungerà al http risposta in formato json, vedere il codice seguente:
@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));
}
}
ho provato l'approccio con due casi di test:
- cercare di consumare un servizio senza fornire intestazioni di autenticazione di base:
Questa è la richiesta/risposta:
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.- Provare a utilizzare lo stesso servizio ma ora l'invio di base autentico intestazioni ation con una password errata:
Questa è la richiesta/risposta:
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>
Come si può vedere nel primo caso il punto di ingresso è stato raggiunto e il metodo commence è stato eseguito con la corretta gestione del eccezione e la risposta JSON è stata restituita. Ma questo non è accaduto quando la password era sbagliata.
Nei registri ho scoperto che entrambi i casi stanno producendo un flusso diverso:
Per il caso 1 (nessuna intestazione auth):
...
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.sprin[email protected]9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]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: [email protected], returned: -1
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: [email protected]ef7, 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
...
Per il caso 2 (password errata):
...
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
...
Il primo caso genera una AccessDeniedException che viene catturata e inviata al metodo di inizio nel mio punto di ingresso, ma il secondo caso che genera una eccezione di BadCredentials non si avvia o il punto di ingresso.
La cosa strana qui è che il metodo Commence dovrebbe ricevere un AuthenticationException, ma AccessDeniedException non è un AuthenticationException ma BadCredentialsException è, vedere l'albero di ereditarietà dalla sicurezza primavera documentazione 3.1 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
Perché il metodo di inizio viene chiamato con un'eccezione che non è del tipo corretto e perché non viene chiamata quando si ha l'eccezione BadCredentialsException del tipo corretto?
cura --- Implementata la risposta @Luke
due descritti di soluzioni utilizzano l'AuthenticationEntryPoint personalizzato indicato nella domanda, deve essere modificato scegliendo una delle due opzioni seguenti la configurazione:
Aggiunta di un BASIC_AUTH_FILTER personalizzato:
<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>
O l'aggiunta del punto di ingresso per l'elemento http-base, IMO è la soluzione più pulita:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <http-basic entry-point-ref="authenticationFailedEntryPoint" /> </http>
Informazioni sul problema di eccezione commentato alla fine della domanda, sembra che AccesDeniedException sia stato tradotto da ExceptionTranlationFilter in AuthenticationException. Questo è il motivo per cui il metodo di inizio nel punto di ingresso personalizzato viene chiamato – raspacorp
raspacorp - Come eseguire il metodo "inizio" anche se abbiamo @ExceptionalHandler sul posto? – Prateek
@pashtika Questo è un problema che ho avuto quasi due anni fa e che è stato risolto utilizzando quel punto di accesso personalizzato. Ricordo che i gestori di eccezioni non stavano cogliendo quell'eccezione come succede in una fase iniziale. Tuttavia non so se è stato risolto nelle versioni più recenti del framework, nel qual caso basta aggiungere un gestore di eccezioni appropriato. – raspacorp