栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于自定义注解实现白盒接口方案

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于自定义注解实现白盒接口方案

        项目为提高接口调用安全性和保护敏感数据,往往会选择将请求和响应采取加密方式来处理。本文分享基于自定义注解实现白盒接口的操作实践。

        一、自定义注解类和切面类

package com.example.test.handler;

import com.example.test.common.WhiteBoxCommonVO;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface WhiteBox {

    // 参数类(用来传递加密数据,只有方法参数中有此类或此类的子类才会执行加解密)
    Class request() default WhiteBoxCommonVO.class;
    Class response() default WhiteBoxCommonVO.class;
}



package com.example.test.handler;

import com.alibaba.fastjson.JSONObject;
import com.example.test.common.BaseException;
import com.example.test.utils.ValidateUtil;
import com.example.test.utils.WhiteBoxUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

@Aspect
@Component
@Slf4j
public class WhiteBoxAspect {

    private static final String DATA = "data";
    private static final String GET_DATA = "getData";
    private static final String WHITE_BOX_COMMON = "WhiteBoxCommonVO";

    @Value("${white.box.enable:true}")
    private boolean whiteBoxEnable;
    
    @Pointcut("@within(com.example.test.handler.WhiteBox) || @annotation(com.example.test.handler.WhiteBox)")
    public void pointcut(){}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable{
            // 获取被代理方法参数
            Object[] args = point.getArgs();
            // 获取被代理对象
            Object target = point.getTarget();
            // 获取通知签名
            MethodSignature signature = (MethodSignature )point.getSignature();
            // 获取被代理方法
            Method pointMethod = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            // 获取被代理方法上面的注解@WhiteBox
            WhiteBox whiteBox = pointMethod.getAnnotation(WhiteBox.class);
            // 被代理方法上没有,则说明@WhiteBox注解在被代理类上
            if(null == whiteBox){
                whiteBox = target.getClass().getAnnotation(WhiteBox.class);
            }
            //处理请求
            handleRequest(whiteBox,args);
            Object result = point.proceed(args);
            //返回结果是否加密
            return handleResponse(whiteBox,result);
    }

    
    private void handleRequest(WhiteBox whiteBox,Object[] args){
        try {
            // 获取注解上声明的加解密类
            Class requestClass = whiteBox.request();
            String simpleName = requestClass.newInstance().getClass().getSimpleName();
            //白盒开关打开
            if (whiteBoxEnable){
                //请求加密的:先解密再校验参数
                if (!WHITE_BOX_COMMON.equals(simpleName)){
                    for (int i = 0; i < args.length; i++) {
                        // 如果是clazz类型则说明请求加密
                        if(requestClass.isInstance(args[i])){
                            //将args[i]转换为clazz表示的类对象
                            Object cast = requestClass.cast(args[i]);
                            Method method = requestClass.getMethod(GET_DATA);
                            // 执行方法,获取加密数据
                            String encryptStr = (String) method.invoke(cast);
                            String json = WhiteBoxUtil.decode(encryptStr);
                            // 转换vo
                            args[i] = JSON.parseObject(json, (Type) args[i].getClass());
                            //参数校验
                            ValidateUtil.validate(args[i]);
                        }
                    }
                }else{
                    //请求不加密的:校验参数
                    validateArg(args);
                }
            }else {
                //白盒开关关闭 且使用@WhiteBox
                if (WHITE_BOX_COMMON.equals(simpleName)){
                    validateArg(args);
                }
            }
        }catch (BaseException e){
            throw e;
        }catch (Exception e) {
            log.error("处理代理对象请求参数异常",e);
            throw new BaseException(HttpStatus.BAD_REQUEST,"10005","处理代理对象请求参数异常",false);
        }
    }

    
    private Object handleResponse(WhiteBox whiteBox,Object result){
        try {
            Class responseClass = whiteBox.response();
            String simpleName = responseClass.newInstance().getClass().getSimpleName();
            if (!WHITE_BOX_COMMON.equals(simpleName) && whiteBoxEnable){
                log.info("返回结果加密前:{}",JSONObject.toJSONString(result));
                String s = WhiteBoxUtil.encode(JSONObject.toJSONString(result));
                log.info("返回结果加密后:{}",s);
                //处理result对象数据返回
                return handleResult(responseClass,s);
            }
            return result;
        }catch (BaseException e){
            throw e;
        }catch (Exception e) {
            log.error("处理代理对象返回结果异常",e);
            throw new BaseException(HttpStatus.BAD_REQUEST,"10004","处理代理对象返回结果异常",false);
        }
    }

    
    private void validateArg(Object[] args){
        if (null != args && args.length > 0){
            for (int i = 0; i < args.length; i++) {
                ValidateUtil.validate(args[i]);
            }
        }
    }

    
    private Object handleResult(Class result,String s) {
        try {
            Object obj = result.newInstance();
            Field field = result.getSuperclass().getDeclaredField(DATA);
            field.setAccessible(true);
            field.set(obj, s);
            return obj;
        } catch (Exception e) {
            log.error("handleResult exception",e);
            throw new BaseException(HttpStatus.BAD_REQUEST,"10001","构造加密返回对象数据",false);
        }
    }
}

        二、关于Request和Response的说明

        请求与响应类TestDriveFormRequest和TestDriveFormResponse需继承WhiteBoxCommonVO,用于接收和响应加密后的数据。

        其中WhiteBoxCommonVO编码如下:

package com.example.test.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WhiteBoxCommonVO {
    @Pattern(regexp = "^.{0,5000}$")
    @Size(max = 5000, message = "data长度不能超过5000个字符")
    private String data;
}

        三、注解启动开关(默认开启)

white.box.enable=true

         四、自定义注解使用方式(可作用在类上或方法上)

①request不加密,response加密:

@WhiteBox(response = TestDriveFormResponse.class)

②request加密,response不加密

@WhiteBox(request = TestDriveFormRequest.class)

③request和response都加密的

@WhiteBox(request = TestDriveFormRequest.class,response = TestDriveFormResponse.class)

④request和response都不加密的

        无需做任何处理。

        五、添加手动校验类ValidateUtil

package com.example.test.utils;

import com.example.test.common.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

@Slf4j
public class ValidateUtil {

    private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    
    public static  void validate(T t){
            Set> constraintViolations = validator.validate(t);
            if (constraintViolations.size() > 0) {
                StringBuilder validateError = new StringBuilder();
                for (ConstraintViolation constraintViolation : constraintViolations) {
                    validateError.append(constraintViolation.getMessage()).append(";");
                }
                log.error("ValidationException,{}",validateError.toString());
                throw new BaseException(HttpStatus.BAD_REQUEST,"10000",validateError.toString(),false);
            }
    }
}

        六、添加全局异常处理类

package com.example.test.common;

import org.springframework.http.HttpStatus;

public class BaseException extends RuntimeException {

    private String code;
    private String message;
    private HttpStatus httpStatus;
    private boolean notification = false;

    public BaseException(HttpStatus httpStatus,String code, String message,boolean notification) {
        super(message);
        this.httpStatus = httpStatus;
        this.code = code;
        this.message = message;
        this.notification = notification;
    }
}

        七、白盒工具类源码

package com.example.test.utils;

import com.example.test.common.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;


public class WhiteBoxUtil {

    public static final Logger log = LoggerFactory.getLogger(WhiteBoxUtil.class);

    
    public static String DEFAULT_SECRET_KEY = "";

    @Value("${aes.secret.key}")
    public void setCocoCompanyId(String secretKey){
        DEFAULT_SECRET_KEY = secretKey;
    }

    private static final String AES = "AES";
    
    private static final byte[] KEY_VI = new byte[]{0x79, 0x60, 0x07, 0x67, 0x39, 0x5f, 0x3d, 0x7c, 0x79, 0x60, 0x07, 0x67,0x39, 0x5f, 0x3d, 0x7c};

    
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";

    private static java.util.Base64.Encoder base64Encoder = java.util.Base64.getEncoder();
    private static java.util.Base64.Decoder base64Decoder = java.util.Base64.getDecoder();

    
    static {
        Security.setProperty("crypto.policy", "unlimited");
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

    
    public static String encode(String content) {
        try {
            javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(parseHexStr2Byte(DEFAULT_SECRET_KEY), AES);
            javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));

            // 获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);

            // 根据密码器的初始化方式加密
            byte[] byteAES = cipher.doFinal(byteEncode);

            // 将加密后的数据转换为字符串
            return base64Encoder.encodeToString(byteAES);
        } catch (Exception e) {
            log.error("白盒工具类加密异常",e);
            throw new BaseException(HttpStatus.BAD_REQUEST,"10002","白盒工具类加密异常",false);
        }
    }

    
    public static String decode(String content){
        try {
            javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(parseHexStr2Byte(DEFAULT_SECRET_KEY), AES);
            javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));

            // 将加密并编码后的内容解码成字节数组
            byte[] byteContent = base64Decoder.decode(content);
            // 解密
            byte[] byteDecode = cipher.doFinal(byteContent);
            return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("白盒工具类解密异常",e);
            throw new BaseException(HttpStatus.BAD_REQUEST,"10003","白盒工具类解密异常",false);
        }
    }

    
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }
}

        八、编写Controller类

package com.example.test.controller;

import com.example.test.common.*;
import com.example.test.handler.WhiteBox;
import com.example.test.utils.WhiteBoxUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.Valid;

@Validated
@RestController
@Api(tags = "demo测试")
public class TestController {

    private final static Logger logger = LoggerFactory.getLogger(TestController.class);
    
    @WhiteBox(response = TestDriveFormResponse.class)
    @GetMapping(value = "/test1")
    @ApiOperation("request不加密,response加密")
    public TestDriveFormResponse test1(@RequestBody TestDriveFormRequest request){
        TestDriveFormResponse response = TestDriveFormResponse.builder()
                .id("1")
                .name("abc")
                .build();
        logger.info("response加密后:{}", WhiteBoxUtil.encode("abc"));
        return response;
    }

    
    @GetMapping(value = "/test2")
    @WhiteBox(request = TestDriveFormRequest.class)
    @ApiOperation("request加密,response不加密")
    public TestDriveFormResponse test2(@RequestBody TestDriveFormRequest request){
        //TODO 业务逻辑
        TestDriveFormResponse response = TestDriveFormResponse.builder()
                .id("1")
                .name("abc")
                .build();
        return response;
    }

    
    @GetMapping(value = "/test3")
    @WhiteBox(request = TestDriveFormRequest.class,response = TestDriveFormResponse.class)
    @ApiOperation("request和response都加密的")
    public TestDriveFormResponse test(@RequestBody TestDriveFormRequest request){
        //TODO 业务逻辑
        TestDriveFormResponse response = TestDriveFormResponse.builder()
                .id("1")
                .name("abc")
                .build();
        return response;
    }

    
    @GetMapping(value = "/test4")
    @ApiOperation("request和response都不加密")
    public TestDriveFormResponse test4(@RequestBody @Valid TestDriveFormRequest request){
        //TODO 业务逻辑
        TestDriveFormResponse response = TestDriveFormResponse.builder()
                .id("1")
                .name("abc")
                .build();
        return response;
    }

}

        九、编写HTTP Client测试文件

### 1.request不加密,response加密
GET http://localhost:8080/my/test1
Accept: **
Cache-Control: no-cache
Content-Type: application/json

{
  "data": "NwCtijSisVq2JKrlfj2PyKjggCKAupwyfYT+ZZcnJczhBBZ6Gwu5XXTRSDcignp5"
}


### 3.request和response都加密
GET http://localhost:8080/my/test3
Accept: **
Cache-Control: no-cache
Content-Type: application/json

{
  "name":"abc",
  "age":10
}

### 5.request和response都加密 ==>>注解加在类上
GET http://localhost:8080/my/test5
Accept: */*
Cache-Control: no-cache
Content-Type: application/json

{
  "data": "NwCtijSisVq2JKrlfj2PyKjggCKAupwyfYT+ZZcnJczhBBZ6Gwu5XXTRSDcignp5"
}

###

         自行配置加解密秘钥串${white.box.enable:true}后,即可测试和验证。

 

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/888508.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号