【接口数据安全】浅谈接口加密处理

背景

接口返回给前端数据存在一些用户隐私(或者公司有价值的数据),这些数据在当下人均程序员的环境下就很危险了(postman就想偷走我的数据?)

目标

  • 请求内容全加密
  • 返回内容全加密
  • 隐藏具体请求路径

image.png

隐藏具体请求路径

  • 在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只是一种方式,可以用自己的加密方式进行加密解密,需要注意家贼

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYyictlT' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片