原理:
1. 使用AES随机生成Key,加解密参数;
2. 使用RSA加解密Key;
具体操作:
1. 前端使用随机Key加密参数,使用固定RSA秘钥加密Key,请求到后端;
2. 后端收到加密Key,以及加密参数,先使用RSA解密Key,再用解密Key解密参数;
3. 返回使用原Key加密返回参数,使用RSA加密Key;
即:
1. 前端请求公钥加密,后端收到私钥解密;
2. 后端返回私钥加密,前端收到公钥解密。
- package cn.nocov.hospital.gateway.filter;
-
- import cn.hutool.core.date.DatePattern;
- import cn.hutool.core.net.URLEncodeUtil;
- import cn.hutool.core.text.CharSequenceUtil;
- import cn.hutool.core.thread.ThreadUtil;
- import cn.hutool.core.util.ReUtil;
- import cn.hutool.core.util.StrUtil;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import cn.nocov.hospital.gateway.config.redis.RedisUtil;
- import cn.nocov.hospital.gateway.util.IpUtil;
- import cn.nocov.hospital.gateway.util.NacosCfgUtil;
- import cn.nocov.hospital.gateway.util.RequestUriUtil;
- import cn.nocov.hospital.gateway.util.RsaAesUtil;
- import cn.nocov.hospital.gateway.util.TokenUtil;
- import cn.nocov.hospital.gateway.util.TokenUtil.ProductPlatformEnum;
- import com.alibaba.fastjson.JSON;
- import com.google.common.collect.Maps;
- import java.net.URI;
- import java.nio.charset.StandardCharsets;
- import java.time.Duration;
- import java.time.LocalDateTime;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
- import javax.annotation.Resource;
- import lombok.extern.slf4j.Slf4j;
- import org.slf4j.MDC;
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- import org.springframework.cloud.gateway.filter.GlobalFilter;
- import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
- import org.springframework.cloud.gateway.support.BodyInserterContext;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.Ordered;
- import org.springframework.core.io.buffer.DataBuffer;
- import org.springframework.http.HttpHeaders;
- import org.springframework.http.HttpMethod;
- import org.springframework.http.MediaType;
- import org.springframework.http.ResponseCookie;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.http.server.reactive.ServerHttpRequest.Builder;
- import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.reactive.function.BodyInserter;
- import org.springframework.web.reactive.function.BodyInserters;
- import org.springframework.web.reactive.function.server.HandlerStrategies;
- import org.springframework.web.reactive.function.server.ServerRequest;
- import org.springframework.web.server.ServerWebExchange;
- import org.springframework.web.util.UriComponentsBuilder;
- import reactor.core.publisher.Flux;
- import reactor.core.publisher.Mono;
-
- /**
- * @author: Zek
- * @date: 2020/8/13 on 4:36 下午
- * @description: 请求过滤器,注意:@Order、Ordered虽然都是执行顺序,但是使用@Order注解会导致url少一位,不知道啥问题
- */
- @Slf4j
- @Configuration
- public class ReqFilter implements GlobalFilter, Ordered {
-
- @Resource private NacosCfgUtil nacosCfgUtil;
- @Resource private RedisUtil redisUtil;
- @Resource private RequestUriUtil requestUriUtil;
-
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - MDC.clear();
- ServerHttpRequest request = exchange.getRequest();
- ServerHttpResponse response = exchange.getResponse();
- if (RequestMethod.OPTIONS.name().equals(request.getMethodValue())) {
- return chain.filter(exchange);
- }
- URI requestUri = request.getURI();
- String uri = requestUri.getPath();
- String url = requestUri.toString();
- HttpHeaders headers = request.getHeaders();
- ProductPlatformEnum appPlatform =
- ProductPlatformEnum.toEnum(headers.getFirst(Constant.HEADER_PLATFORM));
- MDC.put(Constant.HEADER_PLATFORM, appPlatform == null ? "" : appPlatform.name());
- String appVersion = headers.getFirst(Constant.HEADER_VERSION);
- MDC.put(Constant.HEADER_VERSION, appVersion);
- String requestId = headers.getFirst(Constant.HEADER_REQUEST_ID);
- MDC.put(Constant.HEADER_REQUEST_ID, requestId);
- HttpMethod method = request.getMethod();
- log.info(
- "{}--------------------》Send {} To {}\n{}",
- requestId,
- method,
- url,
- headers.toSingleValueMap());
- boolean needEncrypt = requestUriUtil.noTokenNeedEncrypt(uri);
- if (needEncrypt) {
- // 判断请求方式是否需要加密
- needEncrypt = needEncryptJudgeRequest(headers, method);
- if (needEncrypt && !TokenUtil.checkHeader(appPlatform, appVersion, requestId)) {
- return errorReturn(response, requestId, "512", "版本过低,请前往商店更新最新版本");
- }
- } else {
- if (CharSequenceUtil.isNotBlank(requestId)) {
- requestId = TokenUtil.getRequestId();
- }
- }
- if (CharSequenceUtil.isNotBlank(requestId)) {
- response.getHeaders().set(Constant.HEADER_REQUEST_ID, requestId);
- }
- String ip = IpUtil.getIp(request);
- Builder mutate =
- request
- .mutate()
- .header(Constant.HEADER_IP, ip)
- .header(Constant.HEADER_REQUEST_ID, requestId);
- // 需要校验token的接口
- String token = "";
- if (requestUriUtil.needToken(uri)) {
- if (appPlatform == null) {
- log.info("{}--------------------》登录校验失败:appPlatform == null,url:{}", requestId, url);
- return errorReturn(response, requestId, "302", "登录验证失败,请重新登录");
- }
- token = TokenUtil.getToken(request, appPlatform);
- if (CharSequenceUtil.isBlank(token)) {
- log.info("{}--------------------》登录校验失败:cookie token == null/'',url:{}", requestId, url);
- return errorReturn(response, requestId, "302", "登录验证失败,请重新登录");
- }
- String userInfoKey = redisUtil.get(loginRedisKey);
- String redisPlatform = getPlatform(userInfoKey);
- xxxxx;
- String userId = getUserId(userInfoKey);
- MDC.put(Constant.HEADER_USER_ID, userId);
- mutate
- .header(Constant.HEADER_USER_ID, userId)
- .header(Constant.HEADER_VERSION, appVersion)
- .header(Constant.HEADER_PLATFORM, appPlatform.name())
- .header(
- Constant.HEADER_USERNAME,
- URLEncodeUtil.encode(redisUtil.hGet(userInfoKey, "username")));
- }
- if (hasRepeatRequest(token, ip, appPlatform, method + uri)) {
- log.info("{}--------------------》重复请求{}", requestId, method + uri);
- return errorReturn(response, requestId, "512", "请稍等一下喔(勿频繁点击)");
- }
- // post请求时,如果是文件上传之类的请求,不修改请求消息体
- exchange.getAttributes().put(Constant.EXCHANGE_HAS_AES, needEncrypt);
- if (needEncrypt) {
- exchange.getAttributes().put(Constant.EXCHANGE_REQUEST_ID, requestId);
- Map
copyOfContextMap = MDC.getCopyOfContextMap(); - try {
- if (HttpMethod.GET == method) {
- return encryptParamsGet(
- chain,
- exchange,
- request,
- response,
- mutate,
- requestUri,
- requestId,
- appPlatform,
- copyOfContextMap);
- } else {
- return encryptParamsPost(
- chain, exchange, headers, requestId, appPlatform, copyOfContextMap);
- }
- } catch (Exception e) {
- log.error("{}--------------------》加解密失败:", requestId, e);
- return errorReturn(response, requestId, "512", "请求异常");
- }
- }
- return chain.filter(exchange.mutate().request(request).build());
- }
-
- private Mono
encryptParamsGet( - GatewayFilterChain chain,
- ServerWebExchange exchange,
- ServerHttpRequest request,
- ServerHttpResponse response,
- Builder mutate,
- URI requestUri,
- String requestIdValue,
- ProductPlatformEnum appPlatform,
- Map
copyOfContextMap) { - MDC.setContextMap(copyOfContextMap);
- Map
queryParams = request.getQueryParams().toSingleValueMap(); - if (!queryParams.isEmpty() && queryParams.containsKey("v")) {
- String v = queryParams.get("v");
- if (CharSequenceUtil.isBlank(v)) {
- return errorReturn(response, requestIdValue, "512", "请求异常");
- }
- log.info("{}--------------------》原V:{}", requestIdValue, v);
- v = RsaAesUtil.rsaDecryptAesKey(appPlatform, v);
- exchange.getAttributes().put(Constant.EXCHANGE_AES_KEY, v);
- URI uri;
- if (queryParams.containsKey("d")) {
- // 替换查询参数
- String d = queryParams.get("d");
- log.info("{}--------------------》原D:{}", requestIdValue, d);
- String decryptParams =
- CharSequenceUtil.isBlank(d)
- ? ""
- : RsaAesUtil.aesDecryptParams(v.getBytes(StandardCharsets.UTF_8), d);
- log.info("{}--------------------》解D:{}", requestIdValue, decryptParams);
- uri =
- UriComponentsBuilder.fromUri(requestUri)
- .replaceQuery(URLEncodeUtil.encode(decryptParams))
- .build(true)
- .toUri();
- } else {
- uri = UriComponentsBuilder.fromUri(requestUri).replaceQuery("").build(true).toUri();
- }
- ServerHttpRequest build = mutate.uri(uri).build();
- log.info(
- "{}--------------------》重构 {} To {}\n{}",
- requestIdValue,
- build.getMethodValue(),
- build.getURI(),
- build.getHeaders().toSingleValueMap());
- return chain.filter(exchange.mutate().request(build).build());
- } else {
- return errorReturn(response, requestIdValue, "512", "请求异常");
- }
- }
-
- private Mono
encryptParamsPost( - GatewayFilterChain chain,
- ServerWebExchange exchange,
- HttpHeaders httpHeaders,
- String requestIdValue,
- ProductPlatformEnum appPlatform,
- Map
copyOfContextMap) { - // read & modify body
- MDC.setContextMap(copyOfContextMap);
- ServerRequest serverRequest =
- ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
- Mono
modifiedBody = - serverRequest
- .bodyToMono(String.class)
- .flatMap(
- body -> {
- MDC.setContextMap(copyOfContextMap);
- // 对原先的body进行修改操作
- String str = StrUtil.str(body, StandardCharsets.UTF_8);
- log.info("{}--------------------》收参:{}", requestIdValue, str);
- JSONObject jsonObject = JSONUtil.parseObj(str);
- if (JSONUtil.isNull(jsonObject)) {
- return Mono.error(new RuntimeException("请求异常"));
- }
- if (!jsonObject.containsKey("v")) {
- return Mono.error(new RuntimeException("请求异常"));
- }
- String v = jsonObject.getStr("v");
- if (CharSequenceUtil.isBlank(v)) {
- return Mono.error(new RuntimeException("请求异常"));
- }
- v = RsaAesUtil.rsaDecryptAesKey(appPlatform, v);
- exchange.getAttributes().put(Constant.EXCHANGE_AES_KEY, v);
- if (jsonObject.containsKey("d")) {
- String d = jsonObject.getStr("d");
- d =
- CharSequenceUtil.isBlank(d)
- ? JSONUtil.toJsonStr(JSONUtil.createObj())
- : RsaAesUtil.aesDecryptParams(v.getBytes(StandardCharsets.UTF_8), d);
- log.info("{}--------------------》解D:{}", requestIdValue, d);
- return Mono.just(d);
- } else {
- return Mono.empty();
- }
- });
- BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
- HttpHeaders headers = new HttpHeaders();
- headers.putAll(httpHeaders);
- headers.remove(HttpHeaders.CONTENT_LENGTH);
- CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
- return bodyInserter
- .insert(outputMessage, new BodyInserterContext())
- .then(
- Mono.defer(
- () -> {
- MDC.setContextMap(copyOfContextMap);
- ServerHttpRequestDecorator decorator =
- new ServerHttpRequestDecorator(exchange.getRequest()) {
- @Override
- public HttpHeaders getHeaders() {
- long contentLength = headers.getContentLength();
- HttpHeaders httpHeaders = new HttpHeaders();
- httpHeaders.putAll(super.getHeaders());
- if (contentLength > 0L) {
- httpHeaders.setContentLength(contentLength);
- } else {
- httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
- }
- return httpHeaders;
- }
-
- @Override
- public Flux
getBody() { - return outputMessage.getBody();
- }
- };
- log.info(
- "{}--------------------》重构 {} To {}\n{}",
- requestIdValue,
- decorator.getMethodValue(),
- decorator.getURI(),
- decorator.getHeaders().toSingleValueMap());
- return chain.filter(exchange.mutate().request(decorator).build());
- }));
- }
-
- private Mono
errorReturn( - ServerHttpResponse response, String requestId, String code, String msg) {
- if ("302".equals(code)) {
- response.addCookie(
- ResponseCookie.from(Constant.TOKEN, "").path("/").maxAge(Duration.ofSeconds(0L)).build());
- }
- response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
- Map
map = Maps.newHashMapWithExpectedSize(3); - map.put("code", code);
- map.put("msg", msg);
- map.put("obj", null);
- log.info("{}--------------------》异常返回:{}", requestId, map);
- MDC.clear();
- return response.writeWith(
- Flux.just(response.bufferFactory().wrap(JSON.toJSONString(map).getBytes())));
- }
-
- /**
- * 是否加解密
- *
- * @param headers
- * @return
- */
- private boolean needEncryptJudgeRequest(HttpHeaders headers, HttpMethod method) {
- return method == HttpMethod.GET
- || (method == HttpMethod.POST
- && (CharSequenceUtil.containsAny(
- headers.getFirst(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JSON_VALUE)));
- }
-
- /**
- * 检查重复请求
- *
- * @param token 判断唯一请求
- * @param ip 判断唯一请求
- * @param appPlatform 判断唯一请求
- * @param uri
- * @return
- */
- public boolean hasRepeatRequest(
- String token, String ip, ProductPlatformEnum appPlatform, String uri) {
- return xxx;
- }
-
- private String getPlatform(String userInfoKey) {
- return xxx;
- }
-
- private String getUserId(String loginRedisValue) {
- return xxx;
- }
-
- private void asyncUserAgentFun(HttpHeaders headers) {
- log.info(xxx);
- }
-
- /** 顺序:数字越小,越先执行 */
- @Override
- public int getOrder() {
- return -2;
- }
- }
- package cn.nocov.hospital.gateway.filter;
-
- import cn.hutool.core.text.CharSequenceUtil;
- import cn.hutool.core.util.StrUtil;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import cn.nocov.hospital.gateway.util.RsaAesUtil;
- import java.nio.charset.StandardCharsets;
- import java.util.Map;
- import lombok.extern.slf4j.Slf4j;
- import org.reactivestreams.Publisher;
- import org.slf4j.MDC;
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- import org.springframework.cloud.gateway.filter.GlobalFilter;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.Ordered;
- import org.springframework.core.io.buffer.DataBuffer;
- import org.springframework.core.io.buffer.DataBufferFactory;
- import org.springframework.core.io.buffer.DataBufferUtils;
- import org.springframework.core.io.buffer.DefaultDataBufferFactory;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Flux;
- import reactor.core.publisher.Mono;
-
- /**
- * @author: Zek
- * @date: 2020/8/13 on 4:36 下午
- * @description:
- */
- @Slf4j
- @Configuration
- public class RespFilter implements GlobalFilter, Ordered {
-
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - Object objHasAes = exchange.getAttributes().get(Constant.EXCHANGE_HAS_AES);
- Boolean hasAes = objHasAes == null ? null : (Boolean) objHasAes;
- String requestIdValue = (String) exchange.getAttributes().get(Constant.EXCHANGE_REQUEST_ID);
- ServerHttpResponse originalResponse = exchange.getResponse();
- originalResponse
- .getHeaders()
- .set(
- Constant.HEADER_REQUEST_ID,
- CharSequenceUtil.isBlank(requestIdValue) ? "" : requestIdValue);
- if (hasAes == null || !hasAes) {
- clearExchange(exchange);
- return chain.filter(exchange.mutate().response(originalResponse).build());
- }
- String key = (String) exchange.getAttributes().get(Constant.EXCHANGE_AES_KEY);
- log.info("{}--------------------》Exchange:{}", requestIdValue, key);
- Map
copyOfContextMap = MDC.getCopyOfContextMap(); - DataBufferFactory bufferFactory = originalResponse.bufferFactory();
- ServerHttpResponseDecorator decoratedResponse =
- new ServerHttpResponseDecorator(originalResponse) {
- @Override
- public Mono
writeWith(Publisher extends DataBuffer> body) { - if (body instanceof Flux) {
- Flux extends DataBuffer> fluxBody = (Flux extends DataBuffer>) body;
- return super.writeWith(
- fluxBody
- .buffer()
- .map(
- dataBuffer -> {
- MDC.setContextMap(copyOfContextMap);
- DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
- DataBuffer join = dataBufferFactory.join(dataBuffer);
- byte[] content = new byte[join.readableByteCount()];
- join.read(content);
- // 释放掉内存
- DataBufferUtils.release(join);
- String respBody = StrUtil.str(content, StandardCharsets.UTF_8);
- if (!JSONUtil.isTypeJSON(respBody)) {
- log.error(
- "{}--------------------》非JSON:{}", requestIdValue, respBody);
- return bufferFactory.wrap(content);
- }
- JSONObject resp = JSONUtil.parseObj(respBody);
- String obj = resp.getStr("obj");
- if (!JSONUtil.isNull(obj)) {
- resp.set(
- "obj",
- RsaAesUtil.aesEncryptParams(
- key.getBytes(StandardCharsets.UTF_8), obj));
- }
- log.info(
- "{}--------------------》返D:{}",
- requestIdValue,
- (respBody.length() > 1000
- ? respBody.substring(0, 1000) + "......."
- : respBody));
- // 加密后的数据返回给客户端
- return bufferFactory.wrap(JSONUtil.toJsonStr(resp).getBytes());
- }));
- }
- return super.writeWith(body);
- }
- };
- clearExchange(exchange);
- MDC.clear();
- return chain.filter(exchange.mutate().response(decoratedResponse).build());
- }
- /** 顺序:数字越小,越先执行 */
- @Override
- public int getOrder() {
- return -1;
- }
-
- private void clearExchange(ServerWebExchange exchange) {
- exchange.getAttributes().remove(Constant.EXCHANGE_AES_KEY);
- exchange.getAttributes().remove(Constant.EXCHANGE_HAS_AES);
- }
- }
- package cn.nocov.hospital.gateway.util;
-
- import cn.hutool.core.lang.Console;
- import cn.hutool.crypto.SecureUtil;
- import cn.hutool.crypto.asymmetric.KeyType;
- import cn.hutool.crypto.asymmetric.RSA;
- import cn.hutool.crypto.symmetric.AES;
- import cn.nocov.hospital.gateway.util.TokenUtil.ProductPlatformEnum;
- import javax.crypto.spec.IvParameterSpec;
-
- /**
- * @author: Zek
- * @date: 2022/3/8 on 11:05 AM
- * @desc:
- */
- public class RsaAesUtil {
-
- @Deprecated
- private static final RSA XXXX =
- SecureUtil.rsa(
- "xx",
- "xx");
-
- /**
- * AES 解密
- *
- * @param aesKey
- * @param aesParams
- * @return
- */
- public static String aesDecryptParams(byte[] aesKey, String aesParams) {
- AES aes = new AES("CBC", "PKCS7Padding", aesKey, new IvParameterSpec(aesKey, 0, 16).getIV());
- return aes.decryptStr(aesParams);
- }
-
- /**
- * AES 加密
- *
- * @param aesKey
- * @param aesParams
- * @return
- */
- public static String aesEncryptParams(byte[] aesKey, String aesParams) {
- return aes(aesKey).encryptBase64(aesParams);
- }
-
- /**
- * 获取aes
- *
- * @param aesKey
- * @return
- */
- private static AES aes(byte[] aesKey) {
- return new AES("CBC", "PKCS7Padding", aesKey, new IvParameterSpec(aesKey, 0, 16).getIV());
- }
-
- /**
- * rsa 解密 aesKey
- *
- * @param appPlatform
- * @param aesKey
- * @return
- */
- public static String rsaDecryptAesKey(ProductPlatformEnum appPlatform, String aesKey) {
- return getRsa(appPlatform).decryptStr(aesKey, KeyType.PrivateKey);
- }
-
- /**
- * 获取RSA
- *
- * @param appPlatform
- * @return
- */
- private static RSA getRsa(ProductPlatformEnum appPlatform) {
- xxxxxxx
- }
- }
-
- public static void main(String[] args) {
- RSA rsa = SecureUtil.rsa();
- Console.log(rsa.getPrivateKeyBase64());
- Console.log(rsa.getPublicKeyBase64());
- }
- }
会随机产生HTTP method names must be tokens,下篇文章解决(已解决但是不知道原因);