Ich benutze Spring MVCs @ControllerAdvice
und @ExceptionHandler
um alle Ausnahmen einer REST-API zu behandeln. Es funktioniert gut für Ausnahmen, die von Web-MVC-Controllern ausgelöst werden, aber nicht für Ausnahmen, die von benutzerdefinierten Spring Security-Filtern ausgelöst werden, da sie ausgeführt werden, bevor die Controller-Methoden aufgerufen werden.
Ich habe einen benutzerdefinierten Federsicherheitsfilter, der eine tokenbasierte Authentifizierung ausführt:
public class AegisAuthenticationFilter extends GenericFilterBean {
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
...
} catch(AuthenticationException authenticationException) {
SecurityContextHolder.clearContext();
authenticationEntryPoint.commence(request, response, authenticationException);
}
}
}
Mit diesem benutzerdefinierten Einstiegspunkt:
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
Und mit dieser Klasse können Ausnahmen global behandelt werden:
@ControllerAdvice
public class RestEntityResponseExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ InvalidTokenException.class, AuthenticationException.class })
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@ResponseBody
public RestError handleAuthenticationException(Exception ex) {
int errorCode = AegisErrorCode.GenericAuthenticationError;
if(ex instanceof AegisException) {
errorCode = ((AegisException)ex).getCode();
}
RestError re = new RestError(
HttpStatus.UNAUTHORIZED,
errorCode,
"...",
ex.getMessage());
return re;
}
}
Was ich tun muss, ist, einen detaillierten JSON-Body auch für Spring Security AuthenticationException zurückzugeben. Gibt es eine Möglichkeit, Spring Security AuthenticationEntryPoint und Spring MVC @ExceptionHandler zusammenzuarbeiten?
Ich benutze Spring Security 3.1.4 und Spring MVC 3.2.4.
quelle
(@)ExceptionHandler
wird nur funktionieren, wenn die Anfrage von der bearbeitet wirdDispatcherServlet
. Diese Ausnahme tritt jedoch vorher auf, da sie von a ausgelöst wirdFilter
. Sie werden diese Ausnahme also niemals mit einem behandeln können(@)ExceptionHandler
.EntryPoint
. Möglicherweise möchten Sie das Objekt dort erstellen und dort ein Objekt einfügenMappingJackson2HttpMessageConverter
.Antworten:
Ok, ich habe wie vorgeschlagen versucht, den JSON selbst aus dem AuthenticationEntryPoint zu schreiben, und es funktioniert.
Nur zum Testen habe ich den AutenticationEntryPoint durch Entfernen von response.sendError geändert
Auf diese Weise können Sie benutzerdefinierte JSON-Daten zusammen mit dem 401 nicht autorisiert senden, selbst wenn Sie Spring Security AuthenticationEntryPoint verwenden.
Natürlich würden Sie den JSON nicht wie ich zu Testzwecken erstellen, aber Sie würden eine Klasseninstanz serialisieren.
quelle
Dies ist ein sehr interessantes Problem, da Spring Security und Spring Web Framework in der Art und Weise, wie sie mit der Antwort umgehen, nicht ganz konsistent sind. Ich glaube, es muss die Behandlung von Fehlermeldungen
MessageConverter
auf praktische Weise nativ unterstützen .Ich habe versucht, einen eleganten Weg zu finden, um
MessageConverter
Spring Security zu integrieren, damit sie die Ausnahme abfangen und sie entsprechend der Inhaltsverhandlung in einem richtigen Format zurückgeben können . Trotzdem ist meine Lösung unten nicht elegant, aber ich benutze zumindest Spring-Code.Ich gehe davon aus, dass Sie wissen, wie man die Jackson- und JAXB-Bibliothek einbindet, sonst macht es keinen Sinn, fortzufahren. Insgesamt gibt es 3 Schritte.
Schritt 1 - Erstellen Sie eine eigenständige Klasse, in der MessageConverters gespeichert sind
Diese Klasse spielt keine Magie. Es speichert einfach die Nachrichtenkonverter und einen Prozessor
RequestResponseBodyMethodProcessor
. Die Magie steckt in diesem Prozessor, der die gesamte Arbeit erledigt, einschließlich der Aushandlung von Inhalten und der entsprechenden Konvertierung des Antwortkörpers.Schritt 2 - Erstellen Sie AuthenticationEntryPoint
Wie in vielen Tutorials ist diese Klasse für die Implementierung einer benutzerdefinierten Fehlerbehandlung unerlässlich.
Schritt 3 - Registrieren Sie den Einstiegspunkt
Wie bereits erwähnt, mache ich das mit Java Config. Ich zeige hier nur die relevante Konfiguration, es sollte eine andere Konfiguration geben, z. B. Sitzung ohne Status usw.
Versuchen Sie es mit einigen Fällen, in denen die Authentifizierung fehlschlägt. Denken Sie daran, dass der Anforderungsheader Accept: XXX enthalten sollte und Sie die Ausnahme in JSON, XML oder anderen Formaten erhalten sollten.
quelle
InvalidGrantException
aber meine Version von IhnenCustomEntryPoint
wird nicht aufgerufen. Irgendeine Idee, was mir fehlen könnte?AuthenticationEntryPoint
undAccessDeniedHandler
wieUsernameNotFoundException
undInvalidGrantException
kann behandelt werden ,AuthenticationFailureHandler
wie hier erläutert .Der beste Weg, den ich gefunden habe, besteht darin, die Ausnahme an den HandlerExceptionResolver zu delegieren
Anschließend können Sie @ExceptionHandler verwenden, um die Antwort wie gewünscht zu formatieren.
quelle
@ControllerAdvice
Beachten Sie, dass das Übergeben eines Null- Handlers nicht funktioniert, wenn Sie in der Annotation basePackages angegeben haben. Ich musste dies vollständig entfernen, damit der Handler aufgerufen werden konnte.@Component("restAuthenticationEntryPoint")
? Warum braucht man einen Namen wie restAuthenticationEntryPoint? Ist es, um einige Kollisionen mit Spring-Namen zu vermeiden?Im Fall von Spring Boot und
@EnableResourceServer
ist es relativ einfach und bequem,ResourceServerConfigurerAdapter
anstelleWebSecurityConfigurerAdapter
der Java-Konfiguration zu erweitern und eine benutzerdefinierte zu registrieren, indem die MethodeAuthenticationEntryPoint
überschriebenconfigure(ResourceServerSecurityConfigurer resources)
und verwendetresources.authenticationEntryPoint(customAuthEntryPoint())
wird.Etwas wie das:
Es gibt auch eine nette
OAuth2AuthenticationEntryPoint
, die erweitert werden kann (da sie nicht endgültig ist) und teilweise wiederverwendet werden kann, während eine benutzerdefinierte implementiert wirdAuthenticationEntryPoint
. Insbesondere werden "WWW-Authenticate" -Header mit fehlerbezogenen Details hinzugefügt.Hoffe das wird jemandem helfen.
quelle
commence()
Funktion von mirAuthenticationEntryPoint
wird nicht aufgerufen - fehlt mir etwas?Nehmen Sie die Antworten von @Nicola und @Victor Wing und fügen Sie einen standardisierteren Weg hinzu:
Jetzt können Sie konfiguriertes Jackson, Jaxb oder was auch immer Sie verwenden, um Antwortkörper in Ihre MVC-Annotation oder XML-basierte Konfiguration mit ihren Serialisierern, Deserialisierern usw. zu konvertieren.
quelle
<property name="messageConverter" ref="myConverterBeanName"/>
Tag erstellen . Wenn Sie eine@Configuration
Klasse verwenden, verwenden Sie einfach diesetMessageConverter()
Methode.Wir müssen
HandlerExceptionResolver
in diesem Fall verwenden.Außerdem müssen Sie die Exception-Handler-Klasse hinzufügen, um Ihr Objekt zurückzugeben.
können Sie einen Fehler zum Zeitpunkt des bekommen ein Projekt laufen , weil mehrere Implementierungen
HandlerExceptionResolver
, in diesem Fall muss man hinzufügen ,@Qualifier("handlerExceptionResolver")
aufHandlerExceptionResolver
quelle
GenericResponseBean
ist nur Java Pojo, können Sie Ihre eigenen erstellenIch konnte damit umgehen, indem ich einfach die Methode 'unsuccessfulAuthentication' in meinem Filter überschrieb. Dort sende ich eine Fehlerantwort mit dem gewünschten HTTP-Statuscode an den Client.
quelle
Update: Wenn Sie den Code direkt sehen möchten und möchten, habe ich zwei Beispiele für Sie, eines mit Standard-Spring-Sicherheit, nach dem Sie suchen, und das andere mit dem Äquivalent von Reactive Web und Reactive Security:
- Normal Web + Jwt-Sicherheit
- Reaktives Jwt
Das, das ich immer für meine JSON-basierten Endpunkte verwende, sieht folgendermaßen aus:
Der Objekt-Mapper wird zu einer Bean, sobald Sie den Spring-Web-Starter hinzufügen. Ich ziehe es jedoch vor, ihn anzupassen. Hier ist meine Implementierung für ObjectMapper:
Der Standard-AuthenticationEntryPoint, den Sie in Ihrer WebSecurityConfigurerAdapter-Klasse festgelegt haben:
quelle
Passen Sie den Filter an und bestimmen Sie, welche Art von Anomalie es gibt. Es sollte eine bessere Methode als diese geben
}}
quelle
Ich benutze den objectMapper. Jeder Rest Service arbeitet hauptsächlich mit json, und in einer Ihrer Konfigurationen haben Sie bereits einen Objekt-Mapper konfiguriert.
Code ist in Kotlin geschrieben, hoffentlich ist es in Ordnung.
quelle
In der
ResourceServerConfigurerAdapter
Klasse hat der unten stehende Code für mich funktioniert.http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler()).and.csrf()..
funktioniert nicht. Deshalb habe ich es als separaten Anruf geschrieben.Implementierung von AuthenticationEntryPoint zum Abfangen des Ablaufs von Token und des fehlenden Autorisierungsheaders.
quelle