В этом посте я хотел бы поделиться уроком, извлеченным одной из команд в O & B. Они использовали Spring Boot с Spring Security.
По умолчанию все, что защищено Spring Security, отправляется в браузер со следующим заголовком HTTP:
1
|
Cache-Control: no-cache, no-store, max-age= 0 , must-revalidate |
По сути, ответ никогда не будет кэшироваться браузером. Хотя это может показаться неэффективным, на самом деле есть веская причина для такого поведения по умолчанию. Когда один пользователь выходит из системы, мы не хотим, чтобы следующий зарегистрированный пользователь мог видеть ресурсы предыдущего пользователя (и это возможно, если они кэшированы).
По умолчанию имеет смысл ничего не кэшировать и оставить явно включенным кэширование. Но это не хорошо, если ничего не кэшируется, так как это приведет к высокой пропускной способности и медленной загрузке страниц.
Хорошо, что очень легко включить кэширование статического содержимого в Spring Boot (даже с Spring Security). Просто настройте период кеширования. Вот и все!
1
2
3
4
5
|
# Boot 2 .x spring.resources.cache.cachecontrol.max-age= 14400 # Boot 1 .x spring.resources.cache-period= 14400 |
Но есть некоторые ошибки! С некоторыми версиями это не так просто! Позвольте мне объяснить дальше.
Существует несколько способов возврата контента:
- Статическое содержимое с помощью автоматически настраиваемого обработчика статических ресурсов Spring Boot
- Метод контроллера, возвращающий имя представления (например, преобразуется в JSP)
- Метод контроллера, возвращающий
HttpEntity
(илиResponseEntity
)
Включить кэширование статического содержимого
Первый (обслуживающий статический контент) обрабатывается путем настройки указанного свойства (обычно в application.properties
как показано выше).
Установить через HttpServletResponse
Во втором случае метод обработчика контроллера может выбрать установку заголовков «Cache-Control» через параметр метода HttpServletResponse
.
1
2
3
4
5
6
7
8
|
@Controller ... class ... { @RequestMapping (...) public String ...(..., HttpServletResponse response) { response.setHeader( "Cache-Control" , "max-age=14400" ); return ...; // view name } } |
Это работает, пока Spring Security не перезаписывает его.
Установить через HttpEntity / ResponseEntity
В третьем случае метод обработчика контроллера может выбрать установку заголовков «Cache-Control» возвращаемого объекта HTTP.
1
2
3
4
5
6
7
|
@Controller ... class ... { @RequestMapping (...) public ResponseEntity<...> ...(...) { return ResponseEntity.ok().cacheControl(...).body(...); } } |
Это работает, пока Spring Security еще не написал свои собственные заголовки «Cache-Control».
Под капотом
Под капотом
Чтобы понять, когда и почему это работает, вот соответствующие последовательности.
В Spring Security Web 4.0.x с 4.2.0 до 4.2.4 и выше происходит следующая последовательность действий:
-
HeaderWriterFilter
делегируетCacheControlHeadersWriter
для записи заголовков «Cache-Control» (включая «Pragma» и «Expires»), если заголовки кэша не существуют. -
Метод обработчика контроллера (если соответствует) вызывается. Метод может:
- Явно установите заголовок в
HttpServletResponse
. -
Или установите заголовок в возвращенном
HttpEntity
илиResponseEntity
(обратитесь кhandleReturnValue()
объектаHttpEntityMethodProcessor
).- Обратите внимание, что
HttpEntityMethodProcessor
записывает заголовки (изHttpEntity
) в фактический ответ, только если они еще не существуют . Это становится проблемой, поскольку еще в # 1 заголовки уже были установлены.
- Обратите внимание, что
- Явно установите заголовок в
-
Если ни один контроллер не обрабатывает запрос, то автоматически настраивается обработчик статических ресурсов Spring Boot. Он пытается обслуживать статический контент и, если настроен для кэширования, он перезаписывает заголовки «Cache-Control» (и очищает значения заголовков «Pragma» и «Expires», если они есть). Статический обработчик ресурса — это объект
ResourceHttpRequestHandler
(см.applyCacheControl()
в его базовом классеWebContentGenerator
).- Однако в Spring Web MVC 4.2.5
WebContentGenerator
записывает заголовки «Cache-Control» только в том случае, если он не существует! , Это становится проблемой, поскольку еще в # 1 заголовки уже были установлены. - В Spring Web MVC 4.2.6 и выше он добавляет заголовки «Cache-Control», даже если он уже существует. Таким образом, нет проблем, даже если заголовки были установлены в # 1.
- Однако в Spring Web MVC 4.2.5
В Spring Security Web 4.1.x, 4.2.5 и выше (в Spring Boot 1.5.11 используется версия 4.2.5) последовательность изменилась. Это выглядит примерно так:
-
Метод обработчика контроллера (если соответствует) вызывается. Метод может:
- Явно установите заголовок в
HttpServletResponse
. -
Или установите заголовок в возвращенном
HttpEntity
илиResponseEntity
(обратитесь кhandleReturnValue()
объектаHttpEntityMethodProcessor
).- Обратите внимание, что
HttpEntityMethodProcessor
записывает заголовки (изHttpEntity
) в фактический ответ, только если они еще не существуют . Нет проблем, поскольку заголовки еще не установлены.
- Обратите внимание, что
- Явно установите заголовок в
- Если ни один контроллер не обрабатывает запрос, то автоматически настраивается обработчик статических ресурсов Spring Boot. Он пытается обслуживать статический контент и, если настроен для кэширования, он перезаписывает заголовки «Cache-Control» (и очищает значения заголовков «Pragma» и «Expires», если они есть).
-
HeaderWriterFilter
делегируетCacheControlHeadersWriter
для записи заголовков «Cache-Control» (включая «Pragma» и «Expires»), если заголовки кэша не существуют.- Нет проблем, поскольку он не будет перезаписан, если заголовки кэша уже установлены.
Рабочие версии
Выше три случая управления кэшированием работают в Spring Boot 1.5.11 и Spring Boot 2.x. Но в случае, если обновление до этих версий невозможно, просмотрите следующие классы и проверьте, имеет ли он желаемое поведение (используя приведенные выше последовательности):
-
-
-
HeaderWriterFilter
(см. МетодdoFilterInternal
) -
CacheControlHeadersWriter
(см.writeHeaders()
) -
WebContentGenerator
(см.applyCacheControl()
) -
HttpEntityMethodProcessor
(см.handleReturnValue()
)
-
-
Также имейте в виду, что Spring Security Web 4.2.5 и выше запишет следующие HTTP-заголовки (перезаписывайте их, даже если они уже установлены, как, например, в контроллере):
-
-
-
X-Content-Type-Options
черезXContentTypeOptionsHeaderWriter
-
Strict-Transport-Security
черезHstsHeaderWriter
-
X-Frame-Options
черезXFrameOptionsHeaderWriter
-
X-XSS-Protection
помощьюXXssProtectionHeaderWriter
-
-
Это связано с тем, что, в отличие от CacheControlHeadersWriter
, CacheControlHeadersWriter
заголовков для вышеупомянутого не проверяют, существуют ли уже заголовки. Они просто устанавливают свои соответствующие заголовки HTTP. Пожалуйста, обратитесь к их соответствующим классам писателя заголовка и выпуск № 5193 .
Другой вариант — заставить Spring Security игнорировать статические запросы ресурсов. Таким образом, настроенный период кэширования не будет перезаписан.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/css/**" , "/js/**" ); // If the above paths are served by the // Spring Boot auto-configured // static resource request handler, // and a cache period is specified, // then it will have a "Cache-Control" // HTTP header in its response. // And it would NOT get overwritten by Spring Security. } } |
Это все на данный момент. Надеюсь, это прояснит ситуацию.
Смотреть оригинальную статью здесь: Кэширование в Spring Boot с Spring Security
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |