Це дуже цікава проблема, оскільки Spring Security та Spring Web framework не зовсім узгоджуються у тому, як вони обробляють відповідь. Я вважаю, що він повинен власноруч підтримувати обробку повідомлень про помилки MessageConverter
зручним способом.
Я спробував знайти елегантний спосіб внести MessageConverter
в Spring Security, щоб вони могли вловити виняток і повернути їх у правильному форматі відповідно до переговорів щодо вмісту . Тим не менше, моє рішення нижче не елегантне, але принаймні використовуйте код Spring.
Припускаю, ви знаєте, як включити бібліотеку Джексона та JAXB, інакше немає сенсу продовжувати. Всього є 3 кроки.
Крок 1 - Створіть автономний клас, зберігаючи MessageConverters
Цей клас не грає жодної магії. У ньому просто зберігаються перетворювачі повідомлень та процесор RequestResponseBodyMethodProcessor
. Магія знаходиться всередині того процесора, який буде виконувати всю роботу, включаючи узгодження вмісту та відповідну конвертацію тіла відповіді.
public class MessageProcessor { // Any name you like
// List of HttpMessageConverter
private List<HttpMessageConverter<?>> messageConverters;
// under org.springframework.web.servlet.mvc.method.annotation
private RequestResponseBodyMethodProcessor processor;
/**
* Below class name are copied from the framework.
* (And yes, they are hard-coded, too)
*/
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", MessageProcessor.class.getClassLoader());
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", MessageProcessor.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", MessageProcessor.class.getClassLoader());
private static final boolean gsonPresent =
ClassUtils.isPresent("com.google.gson.Gson", MessageProcessor.class.getClassLoader());
public MessageProcessor() {
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
processor = new RequestResponseBodyMethodProcessor(this.messageConverters);
}
/**
* This method will convert the response body to the desire format.
*/
public void handle(Object returnValue, HttpServletRequest request,
HttpServletResponse response) throws Exception {
ServletWebRequest nativeRequest = new ServletWebRequest(request, response);
processor.handleReturnValue(returnValue, null, new ModelAndViewContainer(), nativeRequest);
}
/**
* @return list of message converters
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return messageConverters;
}
}
Крок 2 - Створіть AuthenticationEntryPoint
Як і в багатьох навчальних посібниках, цей клас є важливим для реалізації спеціальної обробки помилок.
public class CustomEntryPoint implements AuthenticationEntryPoint {
// The class from Step 1
private MessageProcessor processor;
public CustomEntryPoint() {
// It is up to you to decide when to instantiate
processor = new MessageProcessor();
}
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
// This object is just like the model class,
// the processor will convert it to appropriate format in response body
CustomExceptionObject returnValue = new CustomExceptionObject();
try {
processor.handle(returnValue, request, response);
} catch (Exception e) {
throw new ServletException();
}
}
}
Крок 3 - Зареєструйте точку входу
Як вже згадувалося, я роблю це за допомогою Java Config. Я просто показую відповідну конфігурацію тут, там повинні бути інші конфігурації, такі як сеанс без стану тощо.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
}
}
Спробуйте використати деякі випадки помилки автентифікації, пам’ятайте, що заголовок запиту повинен містити Accept: XXX, і ви повинні отримати виняток у форматі JSON, XML або деяких інших.
(@)ExceptionHandler
буде працювати, лише якщо запит обробляєтьсяDispatcherServlet
. Однак цей виняток трапляється перед цим, оскільки його видає aFilter
. Отже, ви ніколи не зможете обробити цей виняток за допомогою(@)ExceptionHandler
.