前端VUE+后端(Gateway)加密通信

前端数据加密(VUE)

  • main.js配置
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
import JSEncrypt from 'jsencrypt'
import encrypt from './utils/code.js'

Vue.config.productionTip = false

// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMNRhRdV7BI4MN5buB2Dyj6+dSOEpa6jCiJETtBtwfTuWlerqzdgxvFJHKLrHDscCagHY1X1wXh599LE0fs2nQ8CAwEAAQ=='
 
// 加密
Vue.prototype.encrypt = (txt) => {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // 设置公钥
  return encryptor.encrypt(txt) // 对数据进行加密
}

axios.defaults.baseURL = "http://localhost:8001/api/jijin/lianghua"

// 配置请求拦截器
axios.interceptors.request.use(config => {
	const encryptor = new JSEncrypt()
	encryptor.setPublicKey(publicKey) // 设置公钥
	// 对请求体进行加密
	config.data = encryptor.encrypt(JSON.stringify(config.data))
	return config
})

Vue.prototype.$http = axios


new Vue({
	render: h => h(App),
}).$mount('#app')

  • 发起数据加密后的axios请求
this.$http({
	method: "POST",
	url: "/deco",
	params: {
		params1: this.encrypt("我是参数1"),
		params2: this.encrypt("我是参数2")
	},
	headers:{
		"encrypt": true
	},
	data: {
		body: "我是body",
		test: "我是test",
		flag: 1
	}
}).then(function(result) {
	console.log(result)
}).catch(function(ex){
	console.log(ex)
})
  • 控制面板发送情况截图
    在这里插入图片描述

后端数据解密(网关处)

由于所有请求都经过网关,所以这里放在网关处统一进行拦截和解密,但又不是所有的请求都需要进行解密操作,所以还需要再请求头中添加一个标识符用于标记是否需要进行解密操作

完整代码如下

package work.xiaohong.gateway.config;

import cn.hutool.crypto.asymmetric.KeyType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
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;
import cn.hutool.crypto.asymmetric.RSA;

import java.lang.reflect.Field;
import java.net.URI;

/**
 * @author xiaohong
 * @version 1.0
 * @date 2021/12/22 0022 14:21
 * @description 全局拦截器
 */
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {

    @Value("${fgr.privateKey}")
    private String privateKey;

    /**
     * 拦截所有请求
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 获取是否需要解密标识符
        String encrypt = request.getHeaders().getFirst("encrypt");
        
        //使用私钥获取rsa
        RSA rsa = new RSA(privateKey,null);

        // 获取请求的一些信息
        URI uri = request.getURI();
        HttpMethod method = request.getMethod();


        // 判断是否需要解密
        if(!StringUtils.isEmpty(encrypt)){

            // 解密请求参数

            StringBuilder query = new StringBuilder();
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            MediaType mediaType = request.getHeaders().getContentType();
            for (String s : queryParams.keySet()) {
                String nq = s+"="+rsa.decryptStr(queryParams.getFirst(s),KeyType.PrivateKey)+"&";
                query.append(nq);
            }

            // 替换查询参数
            URI newUri = UriComponentsBuilder.fromUri(uri)
                    .replaceQuery(query.toString())
                    .build(false)
                    .toUri();

            // 解密请求体

            // 重新构造request,参考ModifyRequestBodyGatewayFilterFactory
            ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
            //修改请求体
            Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
                //因为约定了终端传参的格式,所以只考虑json的情况,如果是表单传参,请自行发挥
                if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
                    // 对json格式请求体进行解密
                    String newBody = rsa.decryptStr(body,KeyType.PrivateKey);
                    // 这里修改然后将修改后的字符串设置返回
                    return Mono.just(newBody);
                }else if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
                    // 对纯密文请求体进行解密
                    String newBody = rsa.decryptStr(body,KeyType.PrivateKey);
                    return Mono.just(newBody);
                }else {
                    // 返回空请求体
                    return Mono.empty();
                }
            }).switchIfEmpty(Mono.defer(() -> Mono.empty()));

            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            // 统一设置为json类型,不然部分类型会被转码
            headers.setContentType(MediaType.APPLICATION_JSON);
            // post请求时,如果修改过参数,content-length的值也要重新设置,否则下层微服务获取参数时,会缺失一部分
            headers.remove("Content-Length");
            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

            // 返回重构后的新请求request
            return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage,newUri);
                // 返回新的request
                return chain.filter(exchange.mutate().request(decorator).build());
            })).onErrorResume((throwable) -> this.release(exchange, outputMessage, (Throwable) throwable));

        }

        //放行
        return chain.filter(exchange);
    }


    /**
     * 对request进行重构
     * @param exchange
     * @param headers
     * @param outputMessage
     * @return
     */
    public ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
                                               CachedBodyOutputMessage outputMessage,URI newUri) {
        // 重构uri
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            // 重构请求头
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(headers);
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }

                return httpHeaders;
            }

            //重构uri
            public URI getURI(){
                // 修改路径参数
                return newUri;
            }

            // 重构请求体
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }

        };
    }

    private Mono<Void> release(ServerWebExchange exchange, CachedBodyOutputMessage outputMessage, Throwable throwable) {
        Field cached = ReflectionUtils.findField(outputMessage.getClass(), "cached");
        cached.setAccessible(true);
        try {
            return (boolean)cached.get(outputMessage) ? outputMessage.getBody().map(DataBufferUtils::release).then(Mono.error(throwable)) : Mono.error(throwable);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 排序 越小越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

}

测试

访问流程是
浏览器(加密数据)----> 网关(将数据解密后发送到下游服务)-------> 下游服务获取到解密后的数据

下游接口
在这里插入图片描述

测试开始

  • 浏览器
    在这里插入图片描述

  • 网关
    在这里插入图片描述
    没报错就行

  • 下游服务
    在这里插入图片描述
    成功获取到解密数据
    下面的错误是别处的
    小bug
    下次改(我上次也是这么说的)

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值