Не уверен, что это ошибка Spring 5.0.3 или новая функция для исправления ошибок с моей стороны.
После обновления я получаю эту ошибку. Интересно, что эта ошибка возникает только на моем локальном компьютере. Тот же код в тестовой среде с протоколом HTTPS работает нормально.
Продолжаем ...
Причина, по которой я получаю эту ошибку, заключается в том, что мой URL-адрес для загрузки результирующей страницы JSP /location/thisPage.jsp
. Оценка кода request.getRequestURI()
дает мне результат /WEB-INF/somelocation//location/thisPage.jsp
. Если я исправлю URL-адрес страницы JSP на это location/thisPage.jsp
, все будет работать нормально.
Так что мой вопрос, я должен удалить /
из JSP
пути в коде , потому что это то , что требуется идти вперед. Или Spring
внесла ошибку, поскольку единственное различие между моей машиной и тестовой средой - это протокол HTTP
против HTTPS
.
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized.
at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:123)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:194)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
источник
Ответы:
В документации по безопасности Spring упоминается причина блокировки // в запросе.
Итак, есть два возможных решения -
Шаг 1 Создайте собственный брандмауэр, разрешающий косую черту в URL-адресе.
@Bean public HttpFirewall allowUrlEncodedSlashHttpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); return firewall; }
Шаг 2 Затем настройте этот bean-компонент в веб-безопасности.
@Override public void configure(WebSecurity web) throws Exception { //@formatter:off super.configure(web); web.httpFirewall(allowUrlEncodedSlashHttpFirewall()); .... }
Шаг 2 - необязательный шаг, Spring Boot просто нужно объявить bean-компонент типа
HttpFirewall
источник
setAllowUrlEncodedSlash(true)
у меня не сработало. По-прежнему внутренний методisNormalized
возвращаетсяfalse
при двойном слэше.Я заменил
StrictHttpFirewall
сDefaultHttpFirewall
наличием только следующий код:@Bean public HttpFirewall defaultHttpFirewall() { return new DefaultHttpFirewall(); }
Хорошо работает для меня.
Любой риск при использовании
DefaultHttpFirewall
?источник
StrictHttpFirewall
создать подкласс, чтобы дать немного больше контроля над отклонением URL-адресов, как подробно описано в этом ответе .<sec:http-firewall ref="defaultHttpFirewall"/>
Я столкнулся с той же проблемой:
Проблема возникла на конечных точках, где имя
ModelAndView
представления было определено с помощью предшествующей косой черты . Пример:ModelAndView mav = new ModelAndView("/your-view-here");
Если я убрал косую черту, все заработало. Пример:
ModelAndView mav = new ModelAndView("your-view-here");
Я также провел несколько тестов с RedirectView, и, похоже, он работал с предыдущей косой чертой.
источник
Как только я использовал двойную косую черту при вызове API, я получил ту же ошибку.
Мне пришлось позвонить по адресу http: // localhost: 8080 / getSomething, но я сделал это как http: // localhost: 8080 // getSomething . Я решил это, убрав лишнюю косую черту.
источник
В моем случае, обновленном с spring -securiy-web 3.1.3 до 4.2.12, по умолчанию
defaultHttpFirewall
было изменено сDefaultHttpFirewall
наStrictHttpFirewall
. Поэтому просто определите его в конфигурации XML, как показано ниже:<bean id="defaultHttpFirewall" class="org.springframework.security.web.firewall.DefaultHttpFirewall"/> <sec:http-firewall ref="defaultHttpFirewall"/>
установить
HTTPFirewall
какDefaultHttpFirewall
источник
Нижеприведенное решение представляет собой чистый обходной путь, который не ставит под угрозу безопасность, поскольку мы используем такой же строгий брандмауэр.
Шаги по исправлению следующие:
ШАГ 1. Создайте класс, переопределяющий StrictHttpFirewall, как показано ниже.
package com.biz.brains.project.security.firewall; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.http.HttpMethod; import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.security.web.firewall.FirewalledRequest; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.RequestRejectedException; public class CustomStrictHttpFirewall implements HttpFirewall { private static final Set<String> ALLOW_ANY_HTTP_METHOD = Collections.unmodifiableSet(Collections.emptySet()); private static final String ENCODED_PERCENT = "%25"; private static final String PERCENT = "%"; private static final List<String> FORBIDDEN_ENCODED_PERIOD = Collections.unmodifiableList(Arrays.asList("%2e", "%2E")); private static final List<String> FORBIDDEN_SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B")); private static final List<String> FORBIDDEN_FORWARDSLASH = Collections.unmodifiableList(Arrays.asList("%2f", "%2F")); private static final List<String> FORBIDDEN_BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C")); private Set<String> encodedUrlBlacklist = new HashSet<String>(); private Set<String> decodedUrlBlacklist = new HashSet<String>(); private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods(); public CustomStrictHttpFirewall() { urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH); urlBlacklistsAddAll(FORBIDDEN_BACKSLASH); this.encodedUrlBlacklist.add(ENCODED_PERCENT); this.encodedUrlBlacklist.addAll(FORBIDDEN_ENCODED_PERIOD); this.decodedUrlBlacklist.add(PERCENT); } public void setUnsafeAllowAnyHttpMethod(boolean unsafeAllowAnyHttpMethod) { this.allowedHttpMethods = unsafeAllowAnyHttpMethod ? ALLOW_ANY_HTTP_METHOD : createDefaultAllowedHttpMethods(); } public void setAllowedHttpMethods(Collection<String> allowedHttpMethods) { if (allowedHttpMethods == null) { throw new IllegalArgumentException("allowedHttpMethods cannot be null"); } if (allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) { this.allowedHttpMethods = ALLOW_ANY_HTTP_METHOD; } else { this.allowedHttpMethods = new HashSet<>(allowedHttpMethods); } } public void setAllowSemicolon(boolean allowSemicolon) { if (allowSemicolon) { urlBlacklistsRemoveAll(FORBIDDEN_SEMICOLON); } else { urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); } } public void setAllowUrlEncodedSlash(boolean allowUrlEncodedSlash) { if (allowUrlEncodedSlash) { urlBlacklistsRemoveAll(FORBIDDEN_FORWARDSLASH); } else { urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH); } } public void setAllowUrlEncodedPeriod(boolean allowUrlEncodedPeriod) { if (allowUrlEncodedPeriod) { this.encodedUrlBlacklist.removeAll(FORBIDDEN_ENCODED_PERIOD); } else { this.encodedUrlBlacklist.addAll(FORBIDDEN_ENCODED_PERIOD); } } public void setAllowBackSlash(boolean allowBackSlash) { if (allowBackSlash) { urlBlacklistsRemoveAll(FORBIDDEN_BACKSLASH); } else { urlBlacklistsAddAll(FORBIDDEN_BACKSLASH); } } public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) { if (allowUrlEncodedPercent) { this.encodedUrlBlacklist.remove(ENCODED_PERCENT); this.decodedUrlBlacklist.remove(PERCENT); } else { this.encodedUrlBlacklist.add(ENCODED_PERCENT); this.decodedUrlBlacklist.add(PERCENT); } } private void urlBlacklistsAddAll(Collection<String> values) { this.encodedUrlBlacklist.addAll(values); this.decodedUrlBlacklist.addAll(values); } private void urlBlacklistsRemoveAll(Collection<String> values) { this.encodedUrlBlacklist.removeAll(values); this.decodedUrlBlacklist.removeAll(values); } @Override public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { rejectForbiddenHttpMethod(request); rejectedBlacklistedUrls(request); if (!isNormalized(request)) { request.setAttribute("isNormalized", new RequestRejectedException("The request was rejected because the URL was not normalized.")); } String requestUri = request.getRequestURI(); if (!containsOnlyPrintableAsciiCharacters(requestUri)) { request.setAttribute("isNormalized", new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters.")); } return new FirewalledRequest(request) { @Override public void reset() { } }; } private void rejectForbiddenHttpMethod(HttpServletRequest request) { if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) { return; } if (!this.allowedHttpMethods.contains(request.getMethod())) { request.setAttribute("isNormalized", new RequestRejectedException("The request was rejected because the HTTP method \"" + request.getMethod() + "\" was not included within the whitelist " + this.allowedHttpMethods)); } } private void rejectedBlacklistedUrls(HttpServletRequest request) { for (String forbidden : this.encodedUrlBlacklist) { if (encodedUrlContains(request, forbidden)) { request.setAttribute("isNormalized", new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"")); } } for (String forbidden : this.decodedUrlBlacklist) { if (decodedUrlContains(request, forbidden)) { request.setAttribute("isNormalized", new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"")); } } } @Override public HttpServletResponse getFirewalledResponse(HttpServletResponse response) { return new FirewalledResponse(response); } private static Set<String> createDefaultAllowedHttpMethods() { Set<String> result = new HashSet<>(); result.add(HttpMethod.DELETE.name()); result.add(HttpMethod.GET.name()); result.add(HttpMethod.HEAD.name()); result.add(HttpMethod.OPTIONS.name()); result.add(HttpMethod.PATCH.name()); result.add(HttpMethod.POST.name()); result.add(HttpMethod.PUT.name()); return result; } private static boolean isNormalized(HttpServletRequest request) { if (!isNormalized(request.getRequestURI())) { return false; } if (!isNormalized(request.getContextPath())) { return false; } if (!isNormalized(request.getServletPath())) { return false; } if (!isNormalized(request.getPathInfo())) { return false; } return true; } private static boolean encodedUrlContains(HttpServletRequest request, String value) { if (valueContains(request.getContextPath(), value)) { return true; } return valueContains(request.getRequestURI(), value); } private static boolean decodedUrlContains(HttpServletRequest request, String value) { if (valueContains(request.getServletPath(), value)) { return true; } if (valueContains(request.getPathInfo(), value)) { return true; } return false; } private static boolean containsOnlyPrintableAsciiCharacters(String uri) { int length = uri.length(); for (int i = 0; i < length; i++) { char c = uri.charAt(i); if (c < '\u0020' || c > '\u007e') { return false; } } return true; } private static boolean valueContains(String value, String contains) { return value != null && value.contains(contains); } private static boolean isNormalized(String path) { if (path == null) { return true; } if (path.indexOf("//") > -1) { return false; } for (int j = path.length(); j > 0;) { int i = path.lastIndexOf('/', j - 1); int gap = j - i; if (gap == 2 && path.charAt(i + 1) == '.') { // ".", "/./" or "/." return false; } else if (gap == 3 && path.charAt(i + 1) == '.' && path.charAt(i + 2) == '.') { return false; } j = i; } return true; } }
ШАГ 2. Создайте класс FirewalledResponse
package com.biz.brains.project.security.firewall; import java.io.IOException; import java.util.regex.Pattern; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; class FirewalledResponse extends HttpServletResponseWrapper { private static final Pattern CR_OR_LF = Pattern.compile("\\r|\\n"); private static final String LOCATION_HEADER = "Location"; private static final String SET_COOKIE_HEADER = "Set-Cookie"; public FirewalledResponse(HttpServletResponse response) { super(response); } @Override public void sendRedirect(String location) throws IOException { // TODO: implement pluggable validation, instead of simple blacklisting. // SEC-1790. Prevent redirects containing CRLF validateCrlf(LOCATION_HEADER, location); super.sendRedirect(location); } @Override public void setHeader(String name, String value) { validateCrlf(name, value); super.setHeader(name, value); } @Override public void addHeader(String name, String value) { validateCrlf(name, value); super.addHeader(name, value); } @Override public void addCookie(Cookie cookie) { if (cookie != null) { validateCrlf(SET_COOKIE_HEADER, cookie.getName()); validateCrlf(SET_COOKIE_HEADER, cookie.getValue()); validateCrlf(SET_COOKIE_HEADER, cookie.getPath()); validateCrlf(SET_COOKIE_HEADER, cookie.getDomain()); validateCrlf(SET_COOKIE_HEADER, cookie.getComment()); } super.addCookie(cookie); } void validateCrlf(String name, String value) { if (hasCrlf(name) || hasCrlf(value)) { throw new IllegalArgumentException( "Invalid characters (CR/LF) in header " + name); } } private boolean hasCrlf(String value) { return value != null && CR_OR_LF.matcher(value).find(); } }
ШАГ 3. Создайте собственный фильтр для подавления исключения RejectedException.
package com.biz.brains.project.security.filter; import java.io.IOException; import java.util.Objects; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpHeaders; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; import lombok.extern.slf4j.Slf4j; @Component @Slf4j @Order(Ordered.HIGHEST_PRECEDENCE) public class RequestRejectedExceptionFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { RequestRejectedException requestRejectedException=(RequestRejectedException) servletRequest.getAttribute("isNormalized"); if(Objects.nonNull(requestRejectedException)) { throw requestRejectedException; }else { filterChain.doFilter(servletRequest, servletResponse); } } catch (RequestRejectedException requestRejectedException) { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; log .error( "request_rejected: remote={}, user_agent={}, request_url={}", httpServletRequest.getRemoteHost(), httpServletRequest.getHeader(HttpHeaders.USER_AGENT), httpServletRequest.getRequestURL(), requestRejectedException ); httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND); } } }
ШАГ 4. Добавьте настраиваемый фильтр в цепочку пружинных фильтров в конфигурации безопасности.
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(new RequestRejectedExceptionFilter(), ChannelProcessingFilter.class); }
Теперь, используя вышеуказанное исправление, мы можем обработать
RequestRejectedException
страницу с ошибкой 404.источник
В моем случае проблема была вызвана тем, что я не вошел в систему с помощью Postman, поэтому я открыл соединение на другой вкладке с файлом cookie сеанса, который я взял из заголовков в моем сеансе Chrome.
источник