1. 程式人生 > 其它 >springcloud gateway過濾器

springcloud gateway過濾器

一:過濾器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.roncoo.education.common.core.base.BaseException;
import com.roncoo.education.common.core.enums.ResultEnum;
import com.roncoo.education.common.core.tools.Constants;
import
com.roncoo.education.common.core.tools.JWTUtil; import com.roncoo.education.common.core.tools.MD5Util; import com.roncoo.education.util.RequestAddParaUtils; import com.roncoo.education.util.ServerRequestUtil; import io.netty.buffer.ByteBufAllocator; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.Strings;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; 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.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URI; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * 請求開始前執行 */ @Slf4j public class FilterPre implements GlobalFilter, Ordered { private static final Logger logger = LoggerFactory.getLogger(FilterPre.class); private static final String TOKEN = "token"; private static final String USERNO = "userNo"; private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String traceId = MD5Util.getTraceID(); ServerHttpRequest serverHttpRequest = exchange.getRequest(); HttpMethod method = serverHttpRequest.getMethod(); HttpHeaders httpHeaders = serverHttpRequest.getHeaders(); String token = httpHeaders.getFirst(TOKEN); String ip = ServerRequestUtil.getIpAddr(serverHttpRequest); String url = serverHttpRequest.getURI().toString(); log.debug("traceId[" + traceId + "]" + "uri=" + url + " method=" + method + " token=" + token + " ip=" + ip); //驗證請求地址 boolean validateResult = false; //判斷請求是否過濾介面 if (url.contains("/callback")) { // 回撥使用 validateResult = false; } else if (url.startsWith("/zuul")) { // 檔案上傳 validateResult = false; } else if (url.contains("/api")) { // 不鑑權 validateResult = false; } else { validateResult = true; } if (!validateResult) { log.debug("traceId[" + traceId + "]" + "validateResult=" + validateResult + " filter=filter"); return chain.filter(exchange); } Long userNo = getUserNoByToken(token); if (!StringUtils.isEmpty(userNo)) { URI uri = serverHttpRequest.getURI(); if (HttpMethod.POST.equals(method)) { String requestBody = getRequestBody(serverHttpRequest.getBody()); //封裝userNo JSONObject jsonObject = JSON.parseObject(requestBody); log.info("request body is:{}", jsonObject); jsonObject.put(USERNO, userNo); requestBody = jsonObject.toJSONString(); //下面的將請求體再次封裝寫回到request裡,傳到下一級,否則,由於請求體已被消費,後續的服務將取不到值 ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build(); DataBuffer bodyDataBuffer = stringBuffer(requestBody); Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer); // 定義新的訊息頭 HttpHeaders headers = new HttpHeaders(); headers.putAll(exchange.getRequest().getHeaders()); // 新增訊息頭 // headers.set(ServiceConstants.SHIRO_SESSION_PRINCIPALS,GsonUtils.toJson(authenticationVO)); // 由於修改了傳遞引數,需要重新設定CONTENT_LENGTH,長度是位元組長度,不是字串長度 int length = requestBody.getBytes().length; headers.remove(HttpHeaders.CONTENT_LENGTH); headers.setContentLength(length); // // 設定CONTENT_TYPE // if (StringUtils.isEmpty(contentType)) { // headers.set(HttpHeaders.CONTENT_TYPE, contentType); // } request = new ServerHttpRequestDecorator(request) { @Override public HttpHeaders getHeaders() { long contentLength = headers.getContentLength(); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(super.getHeaders()); if (contentLength > 0) { httpHeaders.setContentLength(contentLength); } else { // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); } return httpHeaders; } @Override public Flux<DataBuffer> getBody() { return bodyFlux; } }; //封裝request,傳給下一級 request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(requestBody.length())); return chain.filter(exchange.mutate().request(request).build()); } else { ServerHttpRequest newRequest = RequestAddParaUtils.addPara(exchange, USERNO, userNo.toString()); return chain.filter(exchange.mutate().request(newRequest).build()); } } ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add("Content-Type", "application/json; charset=utf-8"); String json = JSONObject.toJSONString("token失效,請重新登入"); // 解決頁面字符集亂碼 DataBuffer buffer = response.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Flux.just(buffer)); } @Override public int getOrder() { return 0; } /** * token 攔截功能 */ private Long getUserNoByToken(String token) { if (StringUtils.isEmpty(token)) { // token不存在,則從報文裡面獲取 throw new BaseException(ResultEnum.TOKEN_PAST); } // 解析 token DecodedJWT jwt = null; try { jwt = JWTUtil.verify(token); } catch (Exception e) { logger.error("token異常,token={}", token.toString()); throw new BaseException(ResultEnum.TOKEN_ERROR); } // 校驗token if (null == jwt) { throw new BaseException(ResultEnum.TOKEN_ERROR); } Long userNo = JWTUtil.getUserNo(jwt); if (userNo <= 0) { throw new BaseException(ResultEnum.TOKEN_ERROR); } // token正常 String type = JWTUtil.getType(jwt); if (Constants.XCX.equals(type)) { // 小程式請求 // 注意,登入的時候必須要放入快取 if (!stringRedisTemplate.hasKey(Constants.XCX.concat(userNo.toString()))) { // 不存在,則登入異常,有效期為1小時 throw new BaseException(ResultEnum.TOKEN_PAST); } } else { // PC端、安卓端、蘋果端、小程式端 // 單點登入處理,注意,登入的時候必須要放入快取 checkToken(userNo.toString(), token, type); } return userNo; } /** * PC端、安卓端、蘋果端、小程式端 * 校驗 token * * @param userNo * @param token */ private void checkToken(String userNo, String token, String type) { if (!stringRedisTemplate.hasKey(type.concat(userNo))) { // 不存在,則登入異常,有效期為1小時 throw new BaseException(ResultEnum.TOKEN_PAST); } // 存在,判斷是否token相同 String tk = stringRedisTemplate.opsForValue().get(type.concat(userNo)); if (!token.equals(tk)) { // 不同則為不同的使用者登入,這時候提示異地登入 throw new BaseException(ResultEnum.REMOTE_ERROR); } // 更新時間,使token不過期 if (!Constants.PC.equals(type)) { //移動端設定token為30天有效 stringRedisTemplate.opsForValue().set(type.concat(userNo), token, 30, TimeUnit.DAYS); } else { stringRedisTemplate.opsForValue().set(type.concat(userNo), token, 1, TimeUnit.HOURS); } } /** * 用於獲取請求引數 * * @param body * @return */ private static String getRequestBody(Flux<DataBuffer> body) { AtomicReference<String> rawRef = new AtomicReference<>(); body.subscribe(buffer -> { byte[] bytes = new byte[buffer.readableByteCount()]; buffer.read(bytes); DataBufferUtils.release(buffer); rawRef.set(Strings.fromUTF8ByteArray(bytes)); }); return rawRef.get(); } private DataBuffer stringBuffer(String value) { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); buffer.write(bytes); return buffer; } /** * 從Flux<DataBuffer>中獲取字串的方法 */ private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { Flux<DataBuffer> body = serverHttpRequest.getBody(); AtomicReference<String> bodyRef = new AtomicReference<>(); body.subscribe(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); DataBufferUtils.release(buffer); bodyRef.set(charBuffer.toString()); }); return bodyRef.get(); }

二:工具類

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

/**
 * @ClassName RequestAddParaUtils
 * @Author ZhangRF
 * @CreateDate 2021/11/26
 * @Decription 請求新增自定義引數
 */
public class RequestAddParaUtils {
    /**
     * get新增請求引數
     *
     * @param exchange  原有請求
     * @param paraName  引數名稱
     * @param paraValue 引數值
     * @return ServerHttpRequest
     */
    public static ServerHttpRequest addPara(ServerWebExchange exchange, String paraName, String paraValue) {
        URI uri = exchange.getRequest().getURI();
        StringBuilder query = new StringBuilder();
        String originalQuery = uri.getRawQuery();
        if (StringUtils.hasText(originalQuery)) {
            query.append(originalQuery);
            if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
                query.append('&');
            }
        }

        query.append(paraName);
        query.append('=');
        query.append(paraValue);

        URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();
        return exchange.getRequest().mutate().uri(newUri).build();

    }

三:建立過濾器bean及其解決跨域配置

import com.roncoo.education.gateway.common.FilterPre;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
 * @ClassName GatewayConfig
 * @Author ZhangRF
 * @CreateDate 2021/11/23
 * @Decription
 */
@Configuration
public class GatewayConfig {
    @Bean
    public FilterPre requestGlobalFilter() {
        return new FilterPre();
    }

    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //1、配置跨域
        //允許哪些頭進行跨域
        corsConfiguration.addAllowedHeader("*");
        //允許哪些請求方式進行跨域
        corsConfiguration.addAllowedMethod("*");
        //允許哪些請求來源進行跨域
        corsConfiguration.addAllowedOrigin("*");
        //是否允許攜帶cookie進行跨域,否則跨域請求會丟失cookie資訊
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**", corsConfiguration);

        return new CorsWebFilter(source);
    }