Spring 5.0.3 RequestRejectedException: คำร้องขอถูกปฏิเสธเนื่องจาก URL ไม่ได้ถูกทำให้เป็นมาตรฐาน


89

ไม่แน่ใจว่านี่เป็นข้อบกพร่องของ 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ได้แนะนำข้อผิดพลาดเป็นความแตกต่างระหว่างเครื่องและสภาพแวดล้อมในการทดสอบของฉันเป็นโปรโตคอลเมื่อเทียบกับHTTPHTTPS

 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)


1
มีการวางแผนที่จะแก้ไขปัญหาใน 5.1.0; ขณะนี้ 5.0.0 ไม่มีปัญหานี้
java_dude

คำตอบ:


73

เอกสารประกอบการรักษาความปลอดภัยของ Springกล่าวถึงสาเหตุของการบล็อก // ในคำขอ

ตัวอย่างเช่นอาจมีลำดับการส่งผ่านเส้นทาง (เช่น /../) หรือเครื่องหมายทับ (//) หลายตัวซึ่งอาจทำให้การจับคู่รูปแบบล้มเหลว คอนเทนเนอร์บางตัวจะทำให้สิ่งเหล่านี้เป็นปกติก่อนที่จะทำการแมป servlet แต่บางคอนเทนเนอร์ไม่ทำเช่นนั้น เพื่อป้องกันปัญหาเช่นนี้ FilterChainProxy ใช้กลยุทธ์ HttpFirewall เพื่อตรวจสอบและสรุปคำขอ คำขอที่ไม่ได้ทำให้เป็นมาตรฐานจะถูกปฏิเสธโดยอัตโนมัติโดยค่าเริ่มต้นและพารามิเตอร์เส้นทางและเครื่องหมายทับที่ซ้ำกันจะถูกลบออกเพื่อจุดประสงค์ในการจับคู่

ดังนั้นมีสองวิธีที่เป็นไปได้ -

  1. ลบเครื่องหมายทับคู่ (แนวทางที่ต้องการ)
  2. อนุญาต // ใน Spring Security โดยปรับแต่ง StrictHttpFirewall โดยใช้โค้ดด้านล่าง

ขั้นตอนที่ 1 สร้างไฟร์วอลล์แบบกำหนดเองที่อนุญาตให้ใช้เครื่องหมายทับใน URL

@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowUrlEncodedSlash(true);    
    return firewall;
}

ขั้นตอนที่ 2จากนั้นกำหนดค่า bean นี้ใน websecurity

@Override
public void configure(WebSecurity web) throws Exception {
    //@formatter:off
    super.configure(web);
    web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
....
}

ขั้นตอนที่ 2 เป็นขั้นตอนที่ไม่บังคับ Spring Boot เพียงแค่ต้องมีการประกาศประเภท bean HttpFirewallและจะกำหนดค่าอัตโนมัติในตัวกรอง

อัปเดต Spring Security 5.4

ใน Spring security 5.4 ขึ้นไป (Spring Boot> = 2.4.0) เราสามารถกำจัดบันทึกจำนวนมากเกินไปที่บ่นเกี่ยวกับคำขอที่ถูกปฏิเสธโดยการสร้าง bean ด้านล่าง

import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler;

@Bean
RequestRejectedHandler requestRejectedHandler() {
   return new HttpStatusRequestRejectedHandler();
}

ใช่การรักษาความปลอดภัยเส้นทางผ่านได้รับการแนะนำ นั่นเป็นคุณลักษณะใหม่และอาจทำให้เกิดปัญหาได้ ซึ่งฉันก็ไม่แน่ใจเหมือนกันที่คุณเห็นว่ามันทำงานบน HTTPS ไม่ใช่บน HTTP ฉันอยากจะรอจนกว่าข้อบกพร่องนี้จะได้รับการแก้ไขjira.spring.io/browse/SPR-16419
java_dude

อาจเป็นส่วนหนึ่งของปัญหาของเรา ... แต่ ... ผู้ใช้ไม่ได้พิมพ์ใน // ดังนั้นฉันจึงพยายามหาว่าวินาทีนั้น / ถูกเพิ่มเข้ามาอย่างไรในตอนแรก ... ถ้าฤดูใบไม้ผลิกำลังสร้าง jstl url ไม่ควรเพิ่มหรือทำให้เป็นปกติหลังจากเพิ่มแล้ว
xenoterracide

5
สิ่งนี้ไม่สามารถแก้ปัญหาได้จริงอย่างน้อยสำหรับ Spring Security 5.1.1 คุณต้องใช้ DefaultHttpFirewall หากคุณต้องการ URL ที่มีเครื่องหมายทับสองตัวเช่น a / b // c วิธี isNormalized ไม่สามารถกำหนดค่าหรือแทนที่ใน StrictHttpFirewall
Jason Winnebeck

มีโอกาสใดที่จะให้คำแนะนำเกี่ยวกับวิธีการทำใน Spring เพียงอย่างเดียวเมื่อเทียบกับ Boot?
schoon

29

setAllowUrlEncodedSlash(true)ไม่ได้ผลสำหรับฉัน วิธีการภายในยังคงisNormalizedกลับมาfalseเมื่อมีเครื่องหมายทับสองครั้ง

ฉันแทนที่StrictHttpFirewallด้วยDefaultHttpFirewallการมีรหัสต่อไปนี้เท่านั้น:

@Bean
public HttpFirewall defaultHttpFirewall() {
    return new DefaultHttpFirewall();
}

ทำงานได้ดีสำหรับฉัน
ความเสี่ยงใด ๆ โดยใช้DefaultHttpFirewall?


1
ใช่. เพียงเพราะคุณไม่สามารถสร้างกุญแจสำรองให้กับเพื่อนร่วมห้องได้ไม่ได้หมายความว่าคุณควรวางกุญแจดอกเดียวไว้ใต้พรมเช็ดเท้า ไม่แนะนำ ความปลอดภัยไม่ควรเปลี่ยน
java_dude

19
@java_dude เยี่ยมมากที่คุณไม่ได้ให้ข้อมูลหรือเหตุผลใด ๆ เลยเป็นเพียงการเปรียบเทียบที่คลุมเครือ
kaqqao

อีกทางเลือกหนึ่งคือคลาสย่อยStrictHttpFirewallเพื่อให้สามารถควบคุมการปฏิเสธ URL ได้มากขึ้นตามรายละเอียดในคำตอบนี้
vallismortis

1
สิ่งนี้ใช้ได้ผลสำหรับฉัน แต่ฉันต้องเพิ่มสิ่งนี้ใน XML ของ bean ด้วย:<sec:http-firewall ref="defaultHttpFirewall"/>
Jason Winnebeck

1
ความหมายของการใช้โซลูชันนี้คืออะไร?
Felipe Desiderati

10

ฉันพบปัญหาเดียวกันกับ:

เวอร์ชัน Spring Boot = 1.5.10
เวอร์ชัน Spring Security = 4.2.4


ปัญหาที่เกิดขึ้นในปลายทางที่ModelAndViewViewName ถูกกำหนดด้วยก่อนทับ ตัวอย่าง:

ModelAndView mav = new ModelAndView("/your-view-here");

ถ้าฉันลบเครื่องหมายทับมันก็ใช้ได้ดี ตัวอย่าง:

ModelAndView mav = new ModelAndView("your-view-here");

ฉันยังได้ทำการทดสอบกับRedirectViewและดูเหมือนว่าจะใช้ได้กับเครื่องหมายทับไปข้างหน้า


2
นั่นไม่ใช่วิธีแก้ปัญหา จะเกิดอะไรขึ้นถ้านี่เป็นข้อผิดพลาดที่ฝั่ง Spring หากมีการเปลี่ยนแปลงคุณจะต้องยกเลิกการเปลี่ยนแปลงทั้งหมดอีกครั้ง ฉันอยากจะรอจนกว่า 5.1 จะถูกทำเครื่องหมายว่าจะได้รับการแก้ไขในตอนนั้น
java_dude

1
ไม่คุณไม่จำเป็นต้องยกเลิกการเปลี่ยนแปลงเนื่องจากการกำหนด viewName โดยไม่มีเครื่องหมายทับข้างหน้าทำงานได้ดีในเวอร์ชันเก่า
Torsten Ojaperv

นั่นคือสิ่งที่เป็นปัญหา หากใช้งานได้ดีและคุณไม่ได้เปลี่ยนแปลงอะไรเลย Spring ได้แนะนำข้อบกพร่อง เส้นทางควรขึ้นต้นด้วย "/" เสมอ ชำระเงินเอกสารสปริงใด ๆ ลองดูที่github.com/spring-projects/spring-security/issues/5007 & github.com/spring-projects/spring-security/issues/5044
java_dude

1
นี่บิตฉันด้วย การอัปเดต ModelAndView ทั้งหมดโดยไม่มี '/' นำหน้าแก้ไขปัญหา
Nathan Perrier

jira.spring.io/browse/SPR-16740ฉันเปิดจุดบกพร่อง แต่การลบส่วนนำ / ไม่ได้เป็นการแก้ไขสำหรับฉันและในกรณีส่วนใหญ่เราจะส่งคืนชื่อมุมมองเป็นสตริง (จากคอนโทรลเลอร์) . ต้องดูที่มุมมองการเปลี่ยนเส้นทางเป็นวิธีแก้ปัญหา
xenoterracide

6

เมื่อฉันใช้ double slash ในขณะที่เรียก API แล้วฉันก็ได้รับข้อผิดพลาดเดียวกัน

ผมต้องเรียกhttp: // localhost: 8080 / getSomethingแต่ฉันไม่ชอบhttp: // localhost: 8080 // getSomething ฉันแก้ไขได้โดยการลบเครื่องหมายทับพิเศษ


เราสามารถเขียนการจัดการข้อยกเว้นบางอย่างสำหรับสิ่งนี้เพื่อที่เราจะได้แจ้งให้ลูกค้าทราบเกี่ยวกับข้อมูลที่เขาป้อนผิดได้หรือไม่?
YouAreAwesome

5

ในกรณีของฉันอัปเกรดจาก 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
โปรดเพิ่มคำอธิบายลงในโค้ดของคุณเพื่ออธิบายว่าเกิดอะไรขึ้นและทำไม นี่คือการปฏิบัติที่ดี หากคุณไม่ตอบคำตอบของคุณมีความเสี่ยงที่จะถูกลบ มีการแจ้งว่ามีคุณภาพต่ำอยู่แล้ว
herrbischoff

3

วิธีแก้ปัญหาด้านล่างนี้เป็นการแก้ปัญหาที่สะอาดไม่ลดทอนความปลอดภัยเนื่องจากเราใช้ไฟร์วอลล์ที่เข้มงวดเหมือนกัน

ขั้นตอนในการแก้ไขมีดังนี้:

ขั้นตอนที่ 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กับหน้า Error 404 ได้


ขอขอบคุณ. นี่เป็นวิธีที่ฉันใช้ชั่วคราวเพื่อให้เราอัปเกรดไมโครเซอร์วิส Java ของเราจนกว่าแอปส่วนหน้าจะได้รับการอัปเกรดทั้งหมด ฉันไม่ต้องการขั้นตอนที่ 3 และ 4 เพื่อให้ '//' ถือว่าเป็นมาตรฐานได้สำเร็จ ฉันเพิ่งแสดงความคิดเห็นเกี่ยวกับเงื่อนไขที่ตรวจสอบ double-slash ใน isNormalized จากนั้นกำหนดค่า bean ให้ใช้คลาส CustomStrictHttpFirewall แทน
gtaborga

มีวิธีแก้ปัญหาที่ง่ายกว่าผ่าน config หรือไม่? แต่ไม่ต้องปิดไฟร์วอลล์ ..
Prathamesh dhanawade

0

ในกรณีของฉันปัญหาเกิดจากการไม่ได้ลงชื่อเข้าใช้ด้วยบุรุษไปรษณีย์ดังนั้นฉันจึงเปิดการเชื่อมต่อในแท็บอื่นด้วยคุกกี้เซสชันที่ฉันเอามาจากส่วนหัวในเซสชัน Chrome ของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.