SpringBoot项目--后台接口安全加密

近期在项目中因为安全红线要求需要进行接口加解密,在此记录一下。

这里我是使用AES加解密+过滤器方式实现。

废话不多说,直接上代码。

1.创建AES加解密工具类。(此处也可以加上aes的偏移量做进一步的安全性加密)

public class AESUtil {
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";

    public static String encrypt(String plaintext, String secretKey) throws Exception {

        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    public static String decrypt(String ciphertext, String secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        byte[] encryptedBytes = Base64.getDecoder().decode(ciphertext);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

2.创建请求参数解密包装器。(如果前端请求入参没有加密的前提下,可以不用使用此方法。)

@Slf4j
public class ParameterTrimWrapper extends HttpServletRequestWrapper {
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";

    private final String ENCRYPTION_KEY = "***************";


    //用于存放post请求体数据
    private byte[] body;
    private Map<String, String[]> params = new HashMap<>();
    private static final String POST = "POST";
    private static final String GET = "GET";

    public ParameterTrimWrapper(HttpServletRequest request) throws IOException {
        super(request);
        String method = request.getMethod();
        //处理post请求 对请求体进行情况
        if (POST.equalsIgnoreCase(method)) {
            //getInputStream()读取请求体数据流
            String bodyJson = new String(IOUtils.toByteArray(super.getInputStream()), StandardCharsets.UTF_8);
            if (StringUtils.isNotEmpty(bodyJson)) {
                try {
                    log.info("入参解密前: {}", bodyJson);
                    bodyJson = AESUtil.decrypt(bodyJson, ENCRYPTION_KEY);
                    log.info("入参解密后: {}", bodyJson);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                this.body = bodyJson.getBytes(StandardCharsets.UTF_8);
            }
        } else if (GET.equalsIgnoreCase(method)) {
            Map<String, Object> map = new HashMap<>();
            Map<String, String[]> parameterMap = new HashMap<>(request.getParameterMap());
            if (MapUtil.isEmpty(parameterMap)) {
                return;
            }
            String[] params = parameterMap.get("params");
            if (ObjectUtil.isEmpty(params)) {
                return;
            }
            String parameter = String.valueOf(params[0]);
            if (StrUtil.isEmpty(parameter)) {
                return;
            }
            log.info("get入参解密前: {}", parameter);
            // 对加密参数进行解密
            try {
                String decryptedParam = AESUtil.decrypt(parameter, ENCRYPTION_KEY);
                map = JSONUtil.toBean(decryptedParam, Map.class);
                log.info("get入参解密后: {}", decryptedParam);
            } catch (Exception e) {
                e.printStackTrace();
            }
            map.forEach((key, value) -> {
                if (value instanceof String[]) {
                    this.params.put(key, (String[]) value);
                } else if (value instanceof String) {
                    this.params.put(key, new String[]{(String) value});
                } else if (value == null) {
                    this.params.put(key, null);
                } else {
                    this.params.put(key, new String[]{String.valueOf(value)});
                }
            });
            this.params.putAll(request.getParameterMap());
            log.info(request.getParameterMap().toString());
        }
    }

    public String getBody() {
        return new String(this.body, StandardCharsets.UTF_8);
    }

    @Override
    public String[] getParameterValues(String name) {
        return params.get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return this.params;
    }

    @Override
    public String getParameter(String name) {
        String[] values = params.get(name);
        if (values == null || values.length == 0) {
            return null;
        }
        return values[0];
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    //重写getInputStream(),最后程序回来调用这个方法
    @Override
    public ServletInputStream getInputStream() throws IOException {
        //bais处理过后的最新数据流
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() {
                return bais.read();
            }
        };

    }
}


3.自定义加密过滤器。

// 自定义加密过滤器
@Slf4j
public class EncryptionFilter implements Filter {

    // AES加密密钥
    private final String ENCRYPTION_KEY = "*******************";

    // 加密接口列表
    private final Set<String> WHITELIST = new HashSet<>(Arrays.asList(

            "/sso/getUserInfo",  
          
            // 添加其他加密响应数据接口...
    ));

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws ServletException, IOException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        if (isWhitelisted(request.getRequestURI())) {

            // 创建自定义的响应包装器(此处前端请求入参进行加密,需进行参数解密。)
            EncryptionResponseWrapper responseWrapper = new EncryptionResponseWrapper(response);
            log.info("进入请求参数解密: {}", request.getRequestURI());
            ParameterTrimWrapper parameterTrimWrapper = new ParameterTrimWrapper(request);
            log.info("参数解密处理完成");
            filterChain.doFilter(parameterTrimWrapper, responseWrapper);

            // 对私密信息进行加密处理
            String responseData = responseWrapper.getResponseData();
            String encryptedData = null;

            try {
                encryptedData = AESUtil.encrypt(responseData, ENCRYPTION_KEY);
                log.info("加密后的结果:{}", encryptedData);
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                String deRes = AESUtil.decrypt(encryptedData, ENCRYPTION_KEY);
                log.info("解密后的结果:{}", deRes);
            } catch (Exception e) {
                e.printStackTrace();
            }

            //获取加密数据字节数组
            byte[] encryptedBytes = encryptedData.getBytes(StandardCharsets.UTF_8);
            response.setContentType("application/json");
            response.setContentLength(encryptedBytes.length);
            try {
                // 更新响应内容为加密后的数据
                response.getOutputStream().write(encryptedBytes);
                log.info("加密数据响应到到客户端:{}", encryptedData);
                // 刷新流确保数据立即发送到客户端
                response.getOutputStream().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            // 对其他请求进行正常处理
            log.info("非加密接口直接处理: {}", request.getRequestURI());
            filterChain.doFilter(request, response);
            return;
        }
    }



    // 判断请求路径是否在加密接口列表中
    private boolean isWhitelisted(String requestURI) {  
        //使用共同前缀提高白名单匹配的效率
        return WHITELIST.stream().anyMatch(requestURI::startsWith);
    }


    // 自定义加密后的请求包装器
    public class EncryptionResponseWrapper extends HttpServletResponseWrapper {
        private final ByteArrayOutputStream outputStream;
        private final ServletOutputStream servletOutputStream;

        public EncryptionResponseWrapper(HttpServletResponse response) {
            super(response);
            outputStream = new ByteArrayOutputStream();
            servletOutputStream = new ServletOutputStream() {
                @Override
                public void write(int b) throws IOException {
                    outputStream.write(b);
                }

                @Override
                public boolean isReady() {
                    // 根据实际需求返回相应的值
                    return true;
                }

                @Override
                public void setWriteListener(WriteListener writeListener) {
                    // No-op
                }
            };
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return servletOutputStream;
        }

        @Override
        public void flushBuffer() throws IOException {
            servletOutputStream.flush();
        }

        public String getResponseData() {
            return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
        }
    }
}

4.启动类,把加密过滤器bean注册到spring中。

@SpringBootApplication
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class, args);
    }


    // 配置加密过滤器
    @Bean
    public FilterRegistrationBean<EncryptionFilter> encryptionFilter() {
        FilterRegistrationBean<EncryptionFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new EncryptionFilter());
        //配置拦截所有请求地址(此处可以将项目接口路径前缀写入)
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

5.流程,经过以上代码写入之后,前端请求入参加密,后台将加密过滤器注册到spring中,所有请求接口路径在你配置的whiteList中,都将在ParameterTrimWrapper包装器里区分get与post请求进行aes解密,再去进行接口调用,访问数据库查询数据。然后对返回数据进行aes加密到前端,前端解密并在页面渲染展示数据。

6.结尾slogan:生活每天不一样,你要积极又向上!下次见。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Spring Boot、OAuth2.0和JWT Token鉴权认证开发的后台接口是一种使用现代化技术实现的身份验证和授权机制。下面是关于这种后台接口的一些说明: 首先,Spring Boot是一个基于Spring框架的快速开发框架,提供了简化的配置和自动化的特性,使开发者能够更快速高效地开发后台接口。 OAuth2.0是一种开放标准的授权协议,它允许用户授权第三方应用访问他们在资源拥有者上存储的信息,而不需要将用户名和密码提供给第三方。 JWT Token(JSON Web Token)是一种用于在网络应用间安全传递声明的一种方式。它被用作身份验证和授权的令牌,通过加密并以JSON格式存储信息,确保信息的完整性和安全性。 基于以上技术,我们可以开发出具有强大安全认证能力的后台接口。首先,用户在访问接口时,需要提供他们的身份证明,这可以是用户名和密码。接口服务器会使用OAuth2.0协议进行身份验证,并颁发JWT Token给用户。用户在未来的请求中,可以使用该Token进行身份验证,而无需每次都提供用户名和密码。 接口服务器会对JWT Token进行验证,以确保Token的完整性和有效性。如果Token失效或被伪造,访问将被拒绝。如果验证通过,接口服务器会正常处理用户的请求。 使用Spring Boot和OAuth2.0进行开发,可以方便地设置权限和角色。可以根据用户的角色和权限,限制他们对某些资源的访问。 总之,基于Spring Boot、OAuth2.0和JWT Token鉴权认证开发的后台接口提供了一种安全可靠的身份验证和授权机制,能够有效保护后台接口安全性,防止非法访问和数据泄露。这种技术组合在开发现代化的网络应用时非常有用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值