背景
接口返回给前端数据存在一些用户隐私(或者公司有价值的数据),这些数据在当下人均程序员的环境下就很危险了(postman就想偷走我的数据?)
目标
- 请求内容全加密
- 返回内容全加密
- 隐藏具体请求路径
隐藏具体请求路径
- 在header加参数后再nginx进行rewrite
- 在sever中配置 underscores_in_headers on;
- 在请求头增加对应的服务标识 例如:curl -H ‘csid:FFD3E130-8A0E…’ restapi.test.com/v3/service/…
- 在nginx中配置对应地址路由
- 在对应的应用入口增加增加解密方法(如springboot下实现RequestBodyAdvice对body进行解密后再交由@RequestBody转换成对应的对象去执行后续的方法)
location / {
if ($http_csid = "FFD3E130-8A0E...") {
proxy_pass http://10.0.123.123:8888/serviceName/api;
}
}
- 网关层解密请求后分发到对应的handler后去负载到对应的服务商
- 以spring应用为基础的话,在外层controller上对请求进行解密后再分发给对应的handler,以下案例使用RSA进行加密
- 设定请求体结构为
{
"requestId":"0f0e2638-2d4f-de6f-5a08-49b5253a4c45",
"service":"account",
"method":"login",
"timeStamp":"1686453450968",
"body":{
"key":"value"
}
}
- 应用端请求前使用加密方法对整个body进行加密(以js前端为例)
import JSEncrypt from 'jsencrypt';
//需要注意的是,这里的key不应该对应
const publicKeyStr = '公钥,进行解密'
const privateKeyStr = '私钥,进行加密'
const jsRsa = new JSEncrypt()
jsRsa.setPublicKey(publicKeyStr);
jsRsa.setPrivateKey(privateKeyStr);
let requestData={
"data":"requestParamData"
}
let data = JSON.stringify(jsRsa.encrypt( JSON.stringify(requestData)));
//请求
let request = {
method: 'method',
url: 'url',
headers: {
'Content-Type': 'application/json'
},
data : data
};
axios.request(request).then(res=>{
//对请求进行解密
let responseJSON=JSON.parse(jsRsa.decryptLong(response.data));
console.log(responseJSON);
}).catch((error) => {
console.log(error);
});
- 后端接收到请求后,去请求body进行解密
这里需要先对requestBody进行处理,基于springboot项目下,实现RequestBodyAdvice
- 增加方法切面
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RSADecrypt {
//可增加对应的参数管理 比如是否强制需要加密,是否判断超时时间等
}
- 增加加密参数配置类
@ConfigurationProperties(prefix = "encrypt.rsa")
@Configuration
public class SecretProperties {
private String privateKey;
private String publicKey;
private String charset = "UTF-8";
}
- 在配置文件中增加对应的配置
## 是否启用加密
## 这对key不应该对应, 生成方式 PKCS8 1024bit
encrypt.rsa.publicKey = ''
encrypt.rsa.privateKey = ''
- 实现RequestBodyAdvice对请求进行处理
@ControllerAdvice
public class RSAEncryptRequestBodyAdvice implements RequestBodyAdvice {
private Logger log = LoggerFactory.getLogger(this.getClass());
private RSADecrypt RSADecryptAnnotation;
@Autowired
private SecretProperties secretProperties;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Method method = methodParameter.getMethod();
if (Objects.isNull(method)) {
return false;
}
if (method.isAnnotationPresent(RSADecrypt.class) ) {
RSADecryptAnnotation = methodParameter.getMethodAnnotation(RSADecrypt.class);
return true;
}
return false;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
try {
return new RSADecryptHttpInputMessage(inputMessage, secretProperties, RSADecryptAnnotation);
} catch (BusinessException e) {
e.printStackTrace();
log.error("解密失败");
throw new BusinessException(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
log.error("解密异常:{}", e.getMessage());
//该处的BusinessException 继承自runtimeException
throw new BusinessException(e.getMessage());
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
- 增加一个类实现 HttpInputMessage 来处理http消息
@ControllerAdvice
public class RSADecryptHttpInputMessage implements HttpInputMessage {
private Logger log = LoggerFactory.getLogger(this.getClass());
private HttpHeaders httpHeaders;
private InputStream inputStream;
public RSADecryptHttpInputMessage(HttpInputMessage inputMessage, SecretProperties secretProperties, RSADecrypt rsaDecrypt) throws Exception {
//Rsa私钥
String privateKey = secretProperties.getPrivateKey();
//编码格式
String charset = secretProperties.getCharset();
if (StringUtils.isBlank(privateKey)) {
throw new IllegalArgumentException("私钥配置不存在");
}
this.httpHeaders = inputMessage.getHeaders();
String requestBodyContent = new BufferedReader(new InputStreamReader(inputMessage.getBody()))
.lines().collect(Collectors.joining(System.lineSeparator()));
String decryptBody=null;
StringBuilder json = new StringBuilder();
requestBodyContent = requestBodyContent.replaceAll(" ", "+");
if (StringUtils.isNotBlank(requestBodyContent)) {
String[] contents = requestBodyContent.split("\\|");
for (String value : contents) {
try {
value = new String(RSAUtil.decrypt(Base64Util.decode(value), privateKey), charset);
json.append(value);
} catch (Exception e) {
if(Objects.nonNull(this.httpHeaders.get("X-Real-IP")) && this.httpHeaders.get("X-Real-IP").size()>0){
//输出访问者Ip
log.error("非法请求访问ip:{},请求内容:{}",this.httpHeaders.get("X-Real-IP").get(0),requestBodyContent);
}else{
log.error("请求非法:{}", requestBodyContent);
}
throw new BusinessException("非法请求!");
}
}
}
decryptBody = json.toString();
this.inputStream = new ByteArrayInputStream(decryptBody.getBytes());
}
@Override
public InputStream getBody(){
return this.inputStream;
}
@Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
}
- 在这因为我们需要动态去feign,所以我们创建一个动态的service接口
public interface PublicService {
@PostMapping(value = "{path}")
String doPostRequest(@PathVariable("path") String path, @RequestBody Object request);
@GetMapping(value = "{path}")
String doGetRequest(@PathVariable("path") String path, @SpringQueryMap Object request);
}
- 增加一个feign工厂
@Component
public class PublicFeignClientFactory <T>{
private FeignClientBuilder feignClientBuilder;
public PublicFeignClientFactory(ApplicationContext apct){
this.feignClientBuilder = new FeignClientBuilder(apct);
}
public T getClient(final Class<T> type,String Service){
return this.feignClientBuilder.forType(type,Service).build();
}
}
- 在网关入口处对方法增加
@RSADecrypt
注解
@Autowired
private ApplicationContext apct;
@Autowired
private PublicFeignClientFactory pbFeignClientFactory;
//当前接口仅处理post请求
@CrossOrigin
@RSADecrypt
@RequestMapping(value="/gateway",method={RequestMethod.POST})
public Result doGateway(@RequestBody Request request){
//业务处理
// ..
//获取clientName
String feignName=request.getService();
PublicService pubService = pbFeignClientFactory.getClient(PublicService.class,feignName);
//执行feign
String responseJsonBody=pubService.doPostRequest(request.getMethod(),request.getBody());
//包装返回体
return Result(JSONObject.parse(responseJsonBody));
}
写在最后
不过
js前端的话,由于字符串不会进行混淆处理,还需要对key进行拼接或者其他方式的存放(localStroe或者根据用户的id进行加密),RSA只是一种方式,可以用自己的加密方式进行加密解密,需要注意家贼
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END