一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring Cloud Gateway網關XSS過濾方式

Spring Cloud Gateway網關XSS過濾方式

2022-02-17 14:50千年的心 Java教程

這篇文章主要介紹了Spring Cloud Gateway網關XSS過濾方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

XSS是一種經常出現在web應用中的計算機安全漏洞,具體信息請自行Google。本文只分享在Spring Cloud Gateway中執行通用的XSS防范。首次作文,全是代碼,若有遺漏不明之處,請各位看官原諒指點。

使用版本

  • Spring Cloud版本為 Greenwich.SR4
  • Spring Boot版本為 2.1.11.RELEASE

1.創建一個Filter

特別注意的是在處理完成之后需要重新構造請求,否則后續業務無法獲得參數。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import io.netty.buffer.ByteBufAllocator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.constraints.NotEmpty;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
/**
 * XSS過濾
 *
 * @author lieber
 */
@Component
@Slf4j
@ConfigurationProperties("config.xss")
@Data
public class XssFilter implements GlobalFilter, Ordered {
    private List<XssWhiteUrl> whiteUrls;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String method = request.getMethodValue();
        // 判斷是否在白名單中
        if (this.white(uri.getPath(), method)) {
            return chain.filter(exchange);
        }
        // 只攔截POST和PUT請求
        if ((HttpMethod.POST.name().equals(method) || HttpMethod.PUT.name().equals(method))) {
            return DataBufferUtils.join(request.getBody())
                    .flatMap(dataBuffer -> {
                        // 取出body中的參數
                        byte[] oldBytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(oldBytes);
                        String bodyString = new String(oldBytes, StandardCharsets.UTF_8);
                        log.debug("原請求參數為:{}", bodyString);
                        // 執行XSS清理
                        bodyString = XssUtil.INSTANCE.cleanXss(bodyString);
                        log.debug("修改后參數為:{}", bodyString);
                        ServerHttpRequest newRequest = request.mutate().uri(uri).build();
                        // 重新構造body
                        byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);
                        DataBuffer bodyDataBuffer = toDataBuffer(newBytes);
                        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
                        // 重新構造header
                        HttpHeaders headers = new HttpHeaders();
                        headers.putAll(request.getHeaders());
                        // 由于修改了傳遞參數,需要重新設置CONTENT_LENGTH,長度是字節長度,不是字符串長度
                        int length = newBytes.length;
                        headers.remove(HttpHeaders.CONTENT_LENGTH);
                        headers.setContentLength(length);
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8");
                        // 重寫ServerHttpRequestDecorator,修改了body和header,重寫getBody和getHeaders方法
                        newRequest = new ServerHttpRequestDecorator(newRequest) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return bodyFlux;
                            }
                            @Override
                            public HttpHeaders getHeaders() {
                                return headers;
                            }
                        };
                        return chain.filter(exchange.mutate().request(newRequest).build());
                    });
        } else {
            return chain.filter(exchange);
        }
    }
    /**
     * 是否是白名單
     *
     * @param url    路由
     * @param method 請求方式
     * @return true/false
     */
    private boolean white(String url, String method) {
        return whiteUrls != null && whiteUrls.contains(XssWhiteUrl.builder().url(url).method(method).build());
    }
    /**
     * 字節數組轉DataBuffer
     *
     * @param bytes 字節數組
     * @return DataBuffer
     */
    private DataBuffer toDataBuffer(byte[] bytes) {
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }
    public static final int ORDER = 10;
    @Override
    public int getOrder() {
        return ORDER;
    }
    @Data
    @Validated
    @AllArgsConstructor
    @NoArgsConstructor
    private static class XssWhiteUrl {
        @NotEmpty
        private String url;
        @NotEmpty
        private String method;
    }
}

2. 處理XSS字符串

這里大范圍采用Jsoup處理,然后根據自己的業務做了一部分定制。較為特殊的是,我們將字符串中含有'</'標示為這段文本是富文本。在清除xss攻擊字符串方法時優化空間較大。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
 * xss攔截工具類
 *
 * @author lieber
 */
public enum XssUtil {
    /**
     * 實例
     */
    INSTANCE;
    private final static String RICH_TEXT = "</";
    /**
     * 自定義白名單
     */
    private final static Whitelist CUSTOM_WHITELIST = Whitelist.relaxed()
            .addAttributes("video", "width", "height", "controls", "alt", "src")
            .addAttributes(":all", "style", "class");
    /**
     * jsoup不格式化代碼
     */
    private final static Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false);
    /**
     * 清除json對象中的xss攻擊字符
     *
     * @param val json對象字符串
     * @return 清除后的json對象字符串
     */
    private String cleanObj(String val) {
        JSONObject jsonObject = JSONObject.parseObject(val);
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            if (entry.getValue() != null && entry.getValue() instanceof String) {
                String str = (String) entry.getValue();
                str = this.cleanXss(str);
                entry.setValue(str);
            }
        }
        return jsonObject.toJSONString();
    }
    /**
     * 清除json數組中的xss攻擊字符
     *
     * @param val json數組字符串
     * @return 清除后的json數組字符串
     */
    private String cleanArr(String val) {
        List<String> list = JSONObject.parseArray(val, String.class);
        List<String> result = new ArrayList<>(list.size());
        for (String str : list) {
            str = this.cleanXss(str);
            result.add(str);
        }
        return JSONObject.toJSONString(result);
    }
    /**
     * 清除xss攻擊字符串,此處優化空間較大
     *
     * @param str 字符串
     * @return 清除后無害的字符串
     */
    public String cleanXss(String str) {
        if (JsonUtil.INSTANCE.isJsonObj(str)) {
            str = this.cleanObj(str);
        } else if (JsonUtil.INSTANCE.isJsonArr(str)) {
            str = this.cleanArr(str);
        } else {
            boolean richText = this.richText(str);
            if (!richText) {
                str = str.trim();
                str = str.replaceAll(" +", " ");
            }
            String afterClean = Jsoup.clean(str, "", CUSTOM_WHITELIST, OUTPUT_SETTINGS);
            if (paramError(richText, afterClean, str)) {
                throw new BizRunTimeException(ApiCode.PARAM_ERROR, "參數包含特殊字符");
            }
            str = richText ? afterClean : this.backSpecialStr(afterClean);
        }
        return str;
    }
    /**
     * 判斷是否是富文本
     *
     * @param str 待判斷字符串
     * @return true/false
     */
    private boolean richText(String str) {
        return str.contains(RICH_TEXT);
    }
    /**
     * 判斷是否參數錯誤
     *
     * @param richText   是否富文本
     * @param afterClean 清理后字符
     * @param str        原字符串
     * @return true/false
     */
    private boolean paramError(boolean richText, String afterClean, String str) {
        // 如果包含富文本字符,那么不是參數錯誤
        if (richText) {
            return false;
        }
        // 如果清理后的字符和清理前的字符匹配,那么不是參數錯誤
        if (Objects.equals(str, afterClean)) {
            return false;
        }
        // 如果僅僅包含可以通過的特殊字符,那么不是參數錯誤
        if (Objects.equals(str, this.backSpecialStr(afterClean))) {
            return false;
        }
        // 如果還有......
        return true;
    }
    /**
     * 轉義回特殊字符
     *
     * @param str 已經通過轉義字符
     * @return 轉義后特殊字符
     */
    private String backSpecialStr(String str) {
        return str.replaceAll("&amp;", "&");
    }
}

3.其它使用到的工具

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import com.alibaba.fastjson.JSONObject;
import org.springframework.util.StringUtils;
/**
 * JSON處理工具類
 *
 * @author lieber
 */
public enum JsonUtil {
    /**
     * 實例
     */
    INSTANCE;
    /**
     * json對象字符串開始標記
     */
    private final static String JSON_OBJECT_START = "{";
    /**
     * json對象字符串結束標記
     */
    private final static String JSON_OBJECT_END = "}";
    /**
     * json數組字符串開始標記
     */
    private final static String JSON_ARRAY_START = "[";
    /**
     * json數組字符串結束標記
     */
    private final static String JSON_ARRAY_END = "]";
    /**
     * 判斷字符串是否json對象字符串
     *
     * @param val 字符串
     * @return true/false
     */
    public boolean isJsonObj(String val) {
        if (StringUtils.isEmpty(val)) {
            return false;
        }
        val = val.trim();
        if (val.startsWith(JSON_OBJECT_START) && val.endsWith(JSON_OBJECT_END)) {
            try {
                JSONObject.parseObject(val);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }
    /**
     * 判斷字符串是否json數組字符串
     *
     * @param val 字符串
     * @return true/false
     */
    public boolean isJsonArr(String val) {
        if (StringUtils.isEmpty(val)) {
            return false;
        }
        val = val.trim();
        if (StringUtils.isEmpty(val)) {
            return false;
        }
        val = val.trim();
        if (val.startsWith(JSON_ARRAY_START) && val.endsWith(JSON_ARRAY_END)) {
            try {
                JSONObject.parseArray(val);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }
    /**
     * 判斷對象是否是json對象
     *
     * @param obj 待判斷對象
     * @return true/false
     */
    public boolean isJsonObj(Object obj) {
        String str = JSONObject.toJSONString(obj);
        return this.isJsonObj(str);
    }
    /**
     * 判斷字符串是否json字符串
     *
     * @param str 字符串
     * @return true/false
     */
    public boolean isJson(String str) {
        if (StringUtils.isEmpty(str)) {
            return false;
        }
        return this.isJsonObj(str) || this.isJsonArr(str);
    }
}

大功告成。

----------------手動分隔----------------

修改

感謝@chang_p_x的指正,在第一步創建Filter時有問題,原因是使用了新舊代碼的問題,現已經將元代碼放在正文,新代碼如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String method = request.getMethodValue();
        if (this.white(uri.getPath(), method)) {
            return chain.filter(exchange);
        }
        if ((HttpMethod.POST.name().equals(method) || HttpMethod.PUT.name().equals(method))) {
            return DataBufferUtils.join(request.getBody()).flatMap(d -> Mono.just(Optional.of(d))).defaultIfEmpty(Optional.empty())
                    .flatMap(optional -> {
                        // 取出body中的參數
                        String bodyString = "";
                        if (optional.isPresent()) {
                            byte[] oldBytes = new byte[optional.get().readableByteCount()];
                            optional.get().read(oldBytes);
                            bodyString = new String(oldBytes, StandardCharsets.UTF_8);
                        }
                        HttpHeaders httpHeaders = request.getHeaders();
                        // 執行XSS清理
                        log.debug("{} - [{}:{}] XSS處理前參數:{}", method, uri.getPath(), bodyString);
                        bodyString = XssUtil.INSTANCE.cleanXss(bodyString);
                        log.info("{} - [{}:{}] 參數:{}", method, uri.getPath(), bodyString);
                        
                        ServerHttpRequest newRequest = request.mutate().uri(uri).build();
                        // 重新構造body
                        byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);
                        DataBuffer bodyDataBuffer = toDataBuffer(newBytes);
                        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
                        // 重新構造header
                        HttpHeaders headers = new HttpHeaders();
                        headers.putAll(httpHeaders);
                        // 由于修改了傳遞參數,需要重新設置CONTENT_LENGTH,長度是字節長度,不是字符串長度
                        int length = newBytes.length;
                        headers.remove(HttpHeaders.CONTENT_LENGTH);
                        headers.setContentLength(length);
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8");
                        // 重寫ServerHttpRequestDecorator,修改了body和header,重寫getBody和getHeaders方法
                        newRequest = new ServerHttpRequestDecorator(newRequest) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return bodyFlux;
                            }
                            @Override
                            public HttpHeaders getHeaders() {
                                return headers;
                            }
                        };
                        return chain.filter(exchange.mutate().request(newRequest).build());
                    });
        } else {
            return chain.filter(exchange);
        }
    }

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/u010044936/article/details/107067938

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 久久国产主播福利在线 | 肉文小说| 12一14性xxxxx国外| 日本一区二区三区国产 | 亚洲精品97福利在线 | 99热这里只有精品在线播放 | 好姑娘在线视频观看免费 | 五月最新女厕所高跟嘘嘘 | 日本免费观看的视频在线 | 亚洲成人77777 | 婚前试爱免费观看 | 被高跟鞋调教丨vk | 8天堂资源在线官网 | 三级黄色片在线观看 | bl文全肉高h湿被灌尿 | 草嫩社区| 欧式午夜理伦三级在线观看 | 無码一区中文字幕少妇熟女网站 | 精品卡1卡2卡三卡免费网站 | 亚洲久草 | 无套插入| 国产精品夜色视频一级区 | 奶大逼紧| 91精品国产免费久久 | 国产成人亚洲精品91专区高清 | 成人精品在线 | 垫底辣妹免费观看完整版 | 国产清纯91天堂在线观看 | 国产一久久香蕉国产线看观看 | 四虎影视在线永久免费观看 | 男人使劲躁女人小视频 | 99热国产这里只有精品 | 美女脱一光二净的视频 | 9总探花新品牛仔背带裤 | 新影音先锋男人色资源网 | 福利视频导航大全 | 99久久精品免费精品国产 | 蘑菇香蕉茄子绿巨人丝瓜草莓 | 亚洲精品国产精品麻豆99 | 四虎院影永久在线观看 | 波多野结衣黑人系列在线观看 |