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); }