1.任务需求
在工作开发中,需要一个系统可以安卓端登录,也可以后台登录。但需要所有安卓端的请求和返回值都需要加密处理,后台请求值和返回值正常。
2.分析妥妥AOP
1.定义一个安卓方法的注解
//METHOD 说明该注解只能用在方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AndroidMethod {
}
2.根据需求写一个安卓切面
1.把注解 AndroidMethod 作为切点
2.因为需要对请求值和返回值进行加解密,所以使用环绕通知。
3.根据Req的不同,判断数据安卓请求还是后台请求。
@Aspect
@Component
public class AndroidAspect {
@Autowired
private HttpServletRequest httpServletRequest;
//标注切点
@Pointcut("@annotation(com.common.annotation.AndroidMethod)")
public void pointcut() {
}
@Around("pointcut()")
public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {
String req = httpServletRequest.getHeader("Req");
//请求头 Req为1时是后台数据,不加解密,不为1时为安卓数据,需要解密
if (!"1".equals(req)) {
//方法执行前...;
//获取请求参数
Object request = joinPoint.getArgs()[0];
//获取该字段
Field field = request.getClass().getDeclaredField("sign");
//字段属性可访问
field.setAccessible(true);
//加密字段的值
System.out.println("sign-->" + field.get(request));
//解密为接口入参对象
Object decrypt = AesUtil.decrypt(String.valueOf(field.get(request)), request.getClass());
System.out.println("参数值类型:" + joinPoint.getArgs()[0].getClass().getTypeName());
System.out.println("解密的值" + decrypt);
//对象类型的入参直接修改属性即可
}
/*-------------------------------------------------*/
//执行方法,获取返回值
Object obj = joinPoint.proceed();
if (obj instanceof Result) {
Result result = (Result) obj;
if (!"1".equals(req)){
Object data1 = result.get("data");
if (data1 instanceof Page) {
Page page = (Page) data1;
//若不转换,得到的是page 哈希码。
String jsonString = JSONObject.toJSONString(page);
// 对返回值进行处理
String encrypt = AesUtil.encrypt(jsonString);
result.put("data", encrypt);
}
}
return result;
}
// 对字段值进行其他操作
return "";
}
}
3.加密方式:AES ECB模式
用了点泛型,将密文转换为入参对象。
@Component
public class AesUtil {
private static String aesKey;
@Value("${aesKey}")
public void setAesKey(String aesKey) {
AesUtil.aesKey = aesKey;
}
/** 加密模式之 ECB,算法/模式/补码方式 */
private static final String AES_ECB = "AES/ECB/PKCS5Padding";
public static String encrypt(Object param) {
try {
// 创建密码器
Cipher cipher = Cipher.getInstance(AES_ECB);
byte[] byteContent = String.valueOf(param).getBytes("utf-8");
// 初始化为加密模式的密码器
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey.getBytes(StandardCharsets.UTF_8), "AES"));
// 加密
byte[] result = cipher.doFinal(byteContent);
//通过Base64转码返回
return Base64.getEncoder().encodeToString(result);
} catch (Exception ex) {
System.out.println("加密失败");
return null;
}
}
//解密为对应对象
public static <T> T decrypt(String param , Class<T> clazz) throws InstantiationException, IllegalAccessException {
// JSONObject jsonObject = JSONObject.parseObject(param);
//构建
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, aesKey.getBytes(StandardCharsets.UTF_8));
//解密为字符串
String decryptStr = aes.decryptStr(param, CharsetUtil.CHARSET_UTF_8);
//报文转对象
// T t;
// t = clazz.newInstance();
T t = JSONObject.parseObject(decryptStr, clazz);
return t;
}
//解密为对应对象集合
public static <T> List<T> decryptList(String param , Class<T> clazz) throws InstantiationException, IllegalAccessException {
JSONObject jsonObject = JSONObject.parseObject(param);
//构建
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, aesKey.getBytes(StandardCharsets.UTF_8));
//解密为字符串
String decryptStr = aes.decryptStr(param, CharsetUtil.CHARSET_UTF_8);
//报文转对象
List<T> ts = JSONArray.parseArray(decryptStr, clazz);
return ts;
}
}
3.困难点
统一返回对象的data数据中为对象时,得到的不是对象详细信息,而是哈希码。
解决:使用 String jsonString = JSONObject.toJSONString(page); 得到具体数据。
4.测试
分享一个加解密工具:AES在线加密解密工具 - MKLab在线工具
1.安卓端
请求头,不加 Req,走切面 。解密参数 。返回值加密。
解密获得的数据正确
2.后台
请求头加上 Req = 1 ,入参与返回值正常。
测试成功