| | |
| | | // 跨站脚本的 xss 配置,nacos自行添加 |
| | | @Autowired |
| | | private XssProperties xss; |
| | | |
| | | @Value("${security.sign}") |
| | | private boolean parameter_signature; |
| | | |
| | | @Override |
| | | public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) |
| | | { |
| | | public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
| | | ServerHttpRequest request = exchange.getRequest(); |
| | | // xss开关未开启 或 通过nacos关闭,不过滤 |
| | | if (!xss.getEnabled()) |
| | | { |
| | | if (!xss.getEnabled()) { |
| | | return chain.filter(exchange); |
| | | } |
| | | // GET DELETE 不过滤 |
| | | HttpMethod method = request.getMethod(); |
| | | if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) |
| | | { |
| | | if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) { |
| | | return chain.filter(exchange); |
| | | } |
| | | // 非json类型,不过滤 |
| | | if (!isJsonRequest(exchange)) |
| | | { |
| | | if (!isJsonRequest(exchange)) { |
| | | return chain.filter(exchange); |
| | | } |
| | | // excludeUrls 不过滤 |
| | | String url = request.getURI().getPath(); |
| | | if (StringUtils.matches(url, xss.getExcludeUrls())) |
| | | { |
| | | if (StringUtils.matches(url, xss.getExcludeUrls())) { |
| | | return chain.filter(exchange); |
| | | } |
| | | ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange); |
| | | if(parameter_signature && !authSign(httpRequestDecorator)){ |
| | | log.error("[鉴权签名异常处理]请求路径:{}", exchange.getRequest().getPath()); |
| | | return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "签名校验失败", HttpStatus.BAD_REQUEST); |
| | | } |
| | | return chain.filter(exchange.mutate().request(httpRequestDecorator).build()); |
| | | |
| | | } |
| | | |
| | | private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) |
| | | { |
| | | ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) |
| | | { |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) { |
| | | ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) { |
| | | @Override |
| | | public Flux<DataBuffer> getBody() |
| | | { |
| | | public Flux<DataBuffer> getBody() { |
| | | Flux<DataBuffer> body = super.getBody(); |
| | | return body.buffer().map(dataBuffers -> { |
| | | DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public HttpHeaders getHeaders() |
| | | { |
| | | public HttpHeaders getHeaders() { |
| | | HttpHeaders httpHeaders = new HttpHeaders(); |
| | | httpHeaders.putAll(super.getHeaders()); |
| | | // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length |
| | |
| | | * |
| | | * @param exchange HTTP请求 |
| | | */ |
| | | public boolean isJsonRequest(ServerWebExchange exchange) |
| | | { |
| | | public boolean isJsonRequest(ServerWebExchange exchange) { |
| | | String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); |
| | | return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 签名校验 |
| | | * @param httpRequestDecorator |
| | | * @return |
| | | */ |
| | | private boolean authSign(ServerHttpRequestDecorator httpRequestDecorator) { |
| | | HttpHeaders headers = httpRequestDecorator.getHeaders(); |
| | | AtomicReference<JSONObject> jsonObject = new AtomicReference<>(new JSONObject()); |
| | | httpRequestDecorator.getBody().buffer().map(dataBuffers -> { |
| | | DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); |
| | | DataBuffer join = dataBufferFactory.join(dataBuffers); |
| | | byte[] content = new byte[join.readableByteCount()]; |
| | | join.read(content); |
| | | DataBufferUtils.release(join); |
| | | String bodyStr = new String(content, StandardCharsets.UTF_8); |
| | | jsonObject.set(JSON.parseObject(bodyStr)); |
| | | |
| | | // 防xss攻击过滤 |
| | | bodyStr = EscapeUtil.clean(bodyStr); |
| | | // 转成字节 |
| | | byte[] bytes = bodyStr.getBytes(); |
| | | NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); |
| | | DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); |
| | | buffer.write(bytes); |
| | | return buffer; |
| | | }); |
| | | JSONObject params = jsonObject.get(); |
| | | String sign = headers.getFirst(TokenConstants.SING); |
| | | if(StringUtils.isEmpty(sign)){ |
| | | return false; |
| | | } |
| | | String nonce_str = headers.getFirst(TokenConstants.NONCE_STR); |
| | | if(StringUtils.isEmpty(nonce_str)){ |
| | | return false; |
| | | } |
| | | |
| | | String signUrlEncode = localSignUrl(params, nonce_str); |
| | | signUrlEncode = signUrlEncode.replaceAll("& #40;", "\\(") |
| | | .replaceAll("& #41;", "\\)") |
| | | .replaceAll("\\+", " "); |
| | | if(sign.equals(signUrlEncode)){ |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 组装签名路径 |
| | | * @param params |
| | | * @return |
| | | */ |
| | | public static String localSignUrl(JSONObject params, String key) { |
| | | List<String> keySet = new ArrayList<>(params.keySet()); |
| | | // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) |
| | | Collections.sort(keySet, new Comparator<String>() { |
| | | @Override |
| | | public int compare(String o1, String o2) { |
| | | return o1.compareTo(o2); |
| | | } |
| | | }); |
| | | // 构造签名键值对的格式 |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String k : keySet) { |
| | | String v = params.getString(k); |
| | | if(StringUtils.isNotEmpty(v)){ |
| | | sb.append(k + "=" + v + "&"); |
| | | } |
| | | } |
| | | String signUrl = sb.substring(0, sb.length() - 1); |
| | | return signUrlEncode(signUrl, key); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 签名字符串加密 |
| | | * @param signUrl |
| | | * @param encryptKey |
| | | * @return |
| | | */ |
| | | public static String signUrlEncode(String signUrl, String encryptKey) { |
| | | byte[] signByte = new byte[0]; |
| | | try { |
| | | signByte = HMACSHA1.HmacSHA1Encrypt(signUrl, encryptKey); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | String localSign = Base64.encodeBase64String(signByte); |
| | | return localSign; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public int getOrder() |