springboot项目使用拦截器实现一个简单的网关请求透传
需求:做一个网关将外网的请求做一个转化(添加上签名,格式化等操作),然后将请求转发到内网的网关soul-gateway上
1. 思路:采用拦截器拦截掉请求(我这里是拦截固定前缀的请求),然后做一个请求的转化,最后在拦截器中做response返回
1.1 定义的拦截器 继承 handlerInterceptor接口
package com.tyyd.dyhdzzxxxt.gateway.interceptor; import com.tyyd.dyhdzzxxxt.gateway.common.AcwsException; import com.tyyd.dyhdzzxxxt.gateway.config.SoulConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component @Slf4j public class AllRequestHandler implements HandlerInterceptor {
//内网网关的配置 主要是内网地址和端口 @Autowired SoulConfig soulConfig;
//路由处理请求参数的处理类 @Autowired RoutingDelegate routingDelegate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (log.isDebugEnabled()) { long logstart = System.currentTimeMillis(); request.setAttribute("logstart", logstart); } String URL = "http://" + soulConfig.getHost() + ":" + soulConfig.getPort(); ResponseEntity<String> responseEntity = routingDelegate.redirect(request, response, URL, "/api/v1"); if (responseEntity == null) { throw new AcwsException("请求结果异常"); } response.setStatus(responseEntity.getStatusCodeValue()); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter out = null; try { out = response.getWriter(); out.append(responseEntity.getBody()); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { out.close(); } } if (log.isDebugEnabled()) { long logstart = (long) request.getAttribute("logstart"); log.debug("代理请求耗时:{}ms", System.currentTimeMillis() - logstart); } return false; } }
1.2注入定义的拦截器配置 继承webMvcConfigurer接口
package com.tyyd.dyhdzzxxxt.gateway.interceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @Slf4j public class IntercepterConfig implements WebMvcConfigurer {
//注入自定义的 @Autowired private AllRequestHandler addInterceptor; /** * 配置拦截路径 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { //配置拦截的路径 registry.addInterceptor(addInterceptor).addPathPatterns("/api/v1/**"); } }
1.3 处理请求参数的处理类
package com.tyyd.dyhdzzxxxt.gateway.interceptor; import com.tyyd.dyhdzzxxxt.gateway.config.SoulAppConfig; import com.tyyd.dyhdzzxxxt.gateway.config.SoulConfig; import com.tyyd.dyhdzzxxxt.gateway.domain.AppKeyAndAppSecretEntity; import com.tyyd.dyhdzzxxxt.gateway.util.SignUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.List; import java.util.Map; @Component @Slf4j public class RoutingDelegate { //内网网关soulgateway的配置 IP地址和端口等 @Autowired SoulConfig soulConfig; //考虑分布式部署 每个当前的网关都会有单独的 appkey和appsecret 这里配置的就是appkey和appsecret @Autowired SoulAppConfig soulAppConfig; @Autowired RestTemplate restTemplate; public ResponseEntity<String> redirect(HttpServletRequest request, HttpServletResponse response, String routeUrl, String prefix) { try { //构造url String redirectUrl = createRedictUrl(request, routeUrl, prefix); //构造请求头和请求体 RequestEntity requestEntity = createRequestEntity(request, redirectUrl,prefix); //转发请求 return route(requestEntity); } catch (Exception e) { log.error("请求失败", e); return new ResponseEntity("请求失败", HttpStatus.INTERNAL_SERVER_ERROR); } } //构造url和路径参数 private String createRedictUrl(HttpServletRequest request, String routeUrl, String prefix) { String queryString = request.getQueryString(); String url = request.getRequestURI().replaceAll(prefix, ""); return routeUrl + url + (queryString != null ? "?" + queryString : ""); } //构造请求头和请求体 private RequestEntity createRequestEntity(HttpServletRequest request, String url, String prefix) throws URISyntaxException, IOException { String method = request.getMethod(); HttpMethod httpMethod = HttpMethod.resolve(method); MultiValueMap<String, String> headers = parseRequestHeader(request, prefix); byte[] body = parseRequestBody(request); return new RequestEntity<>(body, headers, httpMethod, new URI(url)); } private ResponseEntity<String> route(RequestEntity requestEntity) { log.debug("代理访问:{},参数:{}",requestEntity.getUrl(),requestEntity); ResponseEntity<String> result = restTemplate.exchange(requestEntity, String.class); log.debug("代理访问:{},结果:{}",requestEntity.getUrl(),result.getBody()); return result; } private byte[] parseRequestBody(HttpServletRequest request) throws IOException { InputStream inputStream = request.getInputStream(); return StreamUtils.copyToByteArray(inputStream); } private MultiValueMap<String, String> parseRequestHeader(HttpServletRequest request, String prefix) { HttpHeaders headers = new HttpHeaders(); List<String> headerNames = Collections.list(request.getHeaderNames()); for (String headerName : headerNames) { List<String> headerValues = Collections.list(request.getHeaders(headerName)); for (String headerValue : headerValues) { headers.add(headerName, headerValue); } } String sysId = request.getHeader("sysId"); if(!StringUtils.hasText(sysId)){ sysId = request.getParameter("sysId"); }
//根据传递过来的sysId来选择不同的appkey可secret AppKeyAndAppSecretEntity appKeyAndAppSecretEntity = soulAppConfig.getAppKeyAndAppSecretEntity(Integer.valueOf(sysId)); String url = request.getRequestURI().replaceAll(prefix, "");
//构造签名 Map<String, String> signMap = SignUtils.getInstance().sign(appKeyAndAppSecretEntity, url); headers.add("sign", signMap.get("sign")); headers.add("timestamp", signMap.get("timestamp")); headers.add("version", signMap.get("version")); headers.add("path", url); headers.add("appKey", appKeyAndAppSecretEntity.getAppKey()); headers.add("group", soulConfig.getGroup()); headers.add("tag", soulConfig.getTag()); return headers; } }
签名工具类Signutils:
package com.tyyd.dyhdzzxxxt.gateway.util; import com.tyyd.dyhdzzxxxt.gateway.domain.AppKeyAndAppSecretEntity; import org.springframework.util.DigestUtils; import java.util.*; import java.util.stream.Collectors; public final class SignUtils { private static final SignUtils SIGN_UTILS = new SignUtils(); private SignUtils() { } public static SignUtils getInstance() { return SIGN_UTILS; } public static String generateSign(final String signKey, final Map<String, String> params) { List<String> storedKeys = Arrays.stream(params.keySet() .toArray(new String[]{})) .sorted(Comparator.naturalOrder()) .collect(Collectors.toList()); final String sign = storedKeys.stream() .filter(key -> !Objects.equals(key, "sign")) .map(key -> String.join("", key, params.get(key))) .collect(Collectors.joining()).trim() .concat(signKey); return DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase(); } public Map<String, String> sign(AppKeyAndAppSecretEntity appKeyAndAppSecretEntity , String url){ long now = System.currentTimeMillis(); HashMap<String, String> params = new HashMap<>(); //这里要和内网的保持规则相同 如果要改请同时改 否则可能会导致签名校验不通过 //params.put("timestamp", String.valueOf(new Date())); params.put("timestamp", String.valueOf(now)); params.put("path", url); params.put("version", "1.0.0"); String sign = generateSign(appKeyAndAppSecretEntity.getAppSecret(), params); params.put("sign",sign); return params; } }