项目中要实现接口的统一加解密, 网上看了一圈.感觉都不太合适, 也踩了很多的坑;

有的说用拦截器. 有的说过滤器.还有的说用aop, 当然条条大路通罗马,用什么技术都能实现, 我选了一个看上去更清晰的方案. 用@ControllerAdvice, 当然已经有相关的技术方案,但是没有具体代码, 没说怎么处理request和response, 所有我结合其他文章.整理出这一篇直接可以落地的;

思路

  • 使用 @ControllerAdvice + RequestBodyAdviceAdapter 处理request进行解密;
  • 使用 @ControllerAdvice + ResponseBodyAdvice 对response进行加密;

代码

  • 前置
    这里直接用springboot演示的.默认你会用
    里面用到了Lombok插件; 省略了get,set方法; 你要是不用也可以, 自己生成下get,set就行;

使用了hutool的工具类;

     <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.8.1</version>
            </dependency>

实体类

package com.example.boottest.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 *
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysTestUser implements Serializable {
    /**
     *
     */
    private Long id;

    /**
     *
     */
    private String name;

    /**
     *
     */
    private Integer age;



}

  • 处理request请求 如下:
package com.example.boottest.filter;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import com.example.boottest.domain.SysTestUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

@Slf4j
@ControllerAdvice
public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {

    public static final String KEY = "e75e2d3126357d031ddaf412f9110f66";



    public static void main(String[] args) {
        SysTestUser sysTestUser = new SysTestUser();
        sysTestUser.setAge(12);
        sysTestUser.setId(1L);
        sysTestUser.setName("lily");
        encrypt(JSONUtil.toJsonStr(sysTestUser));

    }

    /**
     * 生成key  用于演示加解密.   这个应该是前后端约定好的;
     *
     */
    private static void getKey() {
        SecretKey aes1 = KeyUtil.generateKey("AES");
        byte[] encoded = aes1.getEncoded();
        String str = HexUtil.encodeHexStr(encoded);
        System.out.println(str);//就是KEY
    }
    /**
     * 加密
     *
     */
    private static void encrypt(String str) {
        //解密
        AES aes = new AES(HexUtil.decodeHex(KEY));
        String encryptHex = aes.encryptHex(str.getBytes(StandardCharsets.UTF_8));
        System.out.println(encryptHex);
    }
    /**
     * 解密
     *
     */
    private static void decrypt(String str) {
        //解密
        AES aes = new AES(HexUtil.decodeHex(KEY));
        String decryptStr = aes.decryptStr(str);
        System.out.println(decryptStr);
    }


    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<?
            extends HttpMessageConverter<?>> converterType) throws IOException {

        HttpHeaders headers = inputMessage.getHeaders();
        //这里的流只能读一次. 后面无法读取了
        byte[] bytes = IoUtil.readBytes(inputMessage.getBody(),false);

        //解密
        AES aes = new AES(HexUtil.decodeHex(KEY));
        byte[] decrypt = aes.decrypt( HexUtil.decodeHex(new String(bytes)));


        //将解密之后的body数据重新封装为HttpInputMessage作为当前方法的返回值
        InputStream inputStream = IoUtil.toStream(decrypt);
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                return inputStream;
            }

            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        };

    }
}



上述的重点是:

 	InputStream inputStream = IoUtil.toStream(decrypt);

– 这一步是把解密后的data转化为新的流

	return new HttpInputMessage() {}

– 重写HttpInputMessage进行返回

  • 处理response 如下:
package com.example.boottest.filter;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;

@Slf4j
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {


    public static final String KEY = "e75e2d3126357d031ddaf412f9110f66";

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        //获取response, 你可能用得到
        ServletServerHttpResponse servletServerHttpResponse =(ServletServerHttpResponse) response;
        HttpServletResponse servletResponse = servletServerHttpResponse.getServletResponse();


        //获取request, 你可能用得到
        ServletServerHttpRequest servletServerHttpRequest =   (ServletServerHttpRequest) request;
        HttpServletRequest servletRequest = servletServerHttpRequest.getServletRequest();


        String jsonStr = JSONUtil.toJsonStr(body);
        log.info("原本的返回值:[{}]",jsonStr);

        //对返回加密;
        AES aes = new AES(HexUtil.decodeHex(KEY));
        byte[] decrypt = aes.encrypt(jsonStr.getBytes(StandardCharsets.UTF_8));

        log.info("加密后的hex:[{}]",HexUtil.encodeHexStr(decrypt));

        return HexUtil.encodeHexStr(decrypt);
    }

}


代码里面的加解密方式我用的是aes进行演示的;你自己可以替换成rsa或者其他;
自己生成了key

    /**
     * 生成key  用于演示加解密.   这个应该是前后端约定好的;
     *
     */
    private static void getKey() {
        SecretKey aes1 = KeyUtil.generateKey("AES");
        byte[] encoded = aes1.getEncoded();
        String str = HexUtil.encodeHexStr(encoded);
        System.out.println(str);//就是KEY
    }

public static final String KEY = “e75e2d3126357d031ddaf412f9110f66”;

  • 准备controller

package com.example.boottest.controller;

import cn.hutool.json.JSONUtil;
import com.example.boottest.domain.SysTestUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletRequest;
import java.io.IOException;


@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    /**
     * test1
     *
     * @return
     */
    @GetMapping("/path")
    public SysTestUser test1(@RequestBody SysTestUser user , ServletRequest servletRequest) throws IOException {
        log.info("user:[{}]", JSONUtil.toJsonStr(user));
        return user;
    }



}

  • 生成前端请求数据
    public static void main(String[] args) {
        SysTestUser sysTestUser = new SysTestUser();
        sysTestUser.setAge(12);
        sysTestUser.setId(1L);
        sysTestUser.setName("lily");
        encrypt(JSONUtil.toJsonStr(sysTestUser));

    }
    /**
     * 加密
     *
     */
    private static void encrypt(String str) {
        //解密
        AES aes = new AES(HexUtil.decodeHex(KEY));
        String encryptHex = aes.encryptHex(str.getBytes(StandardCharsets.UTF_8));
        System.out.println(encryptHex);
    }

生成的数据为:ab04c5d07329c9dfe685167634af14bef36810876f84660494ec7d67c3feda71

测试与结果

  • postman测试
    在这里插入图片描述
  • 后端打印结果

在这里插入图片描述

可以看到:
postman请求时是加密数据–>在DecryptRequestBodyAdvice中被解析成明文.–>传递到controller中被打印出来user对象,–>在EncryptResponseBodyAdvice中再把对象进行加密后返回给postman;

最后

终于完成了接口统一加解密;

当然如果你想拦截特定的请求,也可以在header里面添加特定的值, 进行判断;或者其他方式;

如果帮助到了你. 欢迎点赞收藏,谢谢;

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐