Najpierw chcę komentować, że ja już sprawdziłem inne pytania w przepełnienie stosu i wdrożone moje własne podejście oparte na odpowiedziach: https://stackoverflow.com/a/14425801/2487263 i https://stackoverflow.com/a/16101649/2487263Jak obsługiwać różne wyjątki uwierzytelniania w Spring security 3.1?
staram się zabezpieczyć REST API używane zabezpieczenia Wiosną 3.1 w sposób wiosna 3.2, aplikacja wiosna MVC używam podstawowe podejście uwierzytelniania z prostej konfiguracji:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER"/>
<http-basic />
</http>
Jak widać używam mojego punktu niestandardowego wejścia mam własny obiekt ErrorResponse że dodam do http odpowiedź w formacie json, patrz poniższy kod:
@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));
}
}
Próbowałem podejście z dwóch przypadków testowych:
- starać się spożywać jedną usługę bez podania podstawowych nagłówki uwierzytelniania:
Jest to żądanie/odpowiedź:
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.- Spróbuj spożywać tę samą usługę, ale teraz wysyłaj podstawowe autentyczne Nagłówki acji o złym hasłem:
Jest to żądanie/odpowiedź:
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>
Jak widać w pierwszym przypadku chodzi wpis został osiągnięty i metoda rozpoczynają został wykonany z właściwego obchodzenia się z wyjątek i zwrócono odpowiedź json. Ale tak się nie stało, gdy hasło było błędne.
W logach znalazłem, że oba przypadki produkuje inny przepływ:
Dla przypadku 1 (bez nagłówków 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
...
dla przypadku 2 (złym hasłem):
...
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
...
Pierwszy przypadek powoduje zgłoszenie wyjątku AccessDeniedException, który jest przechwytywany i wysyłany do metody rozpoczęcia w moim punkcie wejścia, ale drugi przypadek powodujący odrzucenie wyjątku BadCredentialsException o punkt wejścia.
Najdziwniejsze tutaj jest to, że metoda rozpoczynają ma otrzymać AuthenticationException, ale AccessDeniedException nie jest AuthenticationException ale BadCredentialsException jest zobaczyć drzewo dziedziczenia z bezpieczeństwem Wiosna dokumentacji 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
Dlaczego rozpoczyna się wywoływana metoda z wyjątkiem, który nie jest poprawnym typem i dlaczego nie jest wywoływana, gdy właściwość BadCredentialsException jest poprawnego typu?
Zmieniano --- Zaimplementowane odpowiedź przez @Luke
Dwa opisane rozwiązania użyć niestandardowego AuthenticationEntryPoint przedstawionego w pytaniu, konfiguracja wymaga modyfikacji wybiera jedną z dwóch następujących opcji:
Dodawanie niestandardowego 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>
Lub dodanie punktu wejścia do elementu http elitarny, IMO jest najczystszym rozwiązanie:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <http-basic entry-point-ref="authenticationFailedEntryPoint" /> </http>
O problemie z wyjątkiem, który został skomentowany na końcu pytania, wydaje się, że wyjątek AccesDeniedException jest tłumaczony za pomocą wyjątku ExceptionTranlationFilter na wyjątek uwierzytelniania. Dlatego metoda rozpoczęcia w niestandardowym punkcie wejścia nazywa się – raspacorp
raspacorp - Jak wykonać metodę "rozpocznij", chociaż mamy @ExceptionalHandler w miejscu? – Prateek
@pashtika Jest to problem, który miałem prawie dwa lata temu i został rozwiązany przy użyciu tego niestandardowego punktu wejścia. Pamiętam, że obsługa wyjątków nie wychwyciła tego wyjątku, jak to się dzieje na początku. Jednak nie wiem, czy jest to poprawione w nowszych wersjach frameworka, w którym to przypadku wystarczy samo dodanie obsługi wyjątku. – raspacorp