springcloud gateway過濾器
阿新 • • 發佈:2021-11-29
一:過濾器
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; importcom.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); }