Spring Boot应用之数据加密以及字段过滤
来源:互联网 发布:php使用html模板 编辑:程序博客网 时间:2024/06/02 06:09
1、应用背景
在使用Spring Boot开发基于restful类型的API时,对于返回的JSON数据我们经常需要对数据进行加密,有的时候我们还必须过滤掉一些对象字段的值来减少网络流量
2、解决方案
1)加密
对返回的数据进行加密,我们必须对spring boot返回json数据前对数据进行拦截和加密处理,为了方便api调用解析还原数据,我们采用双向加密的方式,因为客户端需要解密为明文,加密的使用java本身提供。重点在于在返回数据前进行拦截处理,这时我们可以实现spring boot中的ResponseBodyAdvice接口来打到目的。该接口有两个方法
public interface ResponseBodyAdvice<T> { boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2); T beforeBodyWrite(T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);}
从中可以看出我们着重需要对beforeBodyWrite这个方法进行实现,supports方法的话可以根据自己的需求来确定是否需要使用这个拦截处理
2)数据字段的过滤
对于数据字段的过滤我们这里有两种需求。第一是每个API返回某个对象的数据字段是相同的,比如User对象,每个API需要返回的都是去掉password这个字段,那这种情况我们可以采用JsonView的方式,具体网上可以找到解决方案。第二种需求是对于每一个API返回的某个对象的数据字段不一定相同,都可以通过配置的方式,简单而灵活的达到过滤数据的目的。这时我们的解决方案是在每一个API方法上自定义一个注解,可以配置返回的对象应该包含或者去除哪些字段 ,基于这样的思考我们也可以通过ResponseBodyAdvice中的beforeBodyWrite方法来实现
3、实施方案
1)新建maven项目,添加依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.cyxl</groupId> <artifactId>sprint-boot-responsebodyadvice</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.5.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>
主要是添加spring boot的支持
2) 搭建基本框架
具体各个文件代码如下
User模型
package org.cyxl.model;/** * Created by jeff on 15/10/23. */public class User { private int id; private String email; private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }}
Application启动类
package org.cyxl;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.ComponentScan;/** * Created by jeff on 15/10/23. */@SpringBootApplicationpublic class Application { public static void main(String[] args){ SpringApplication.run(Application.class, args); }}
UserController类
package org.cyxl.controller;import org.cyxl.model.User;import org.cyxl.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;/** * Created by jeff on 15/10/23. */@RestController@RequestMapping("/user")public class UserController { @Autowired UserService userService; @RequestMapping("/{id}") public User findUserById(@PathVariable("id")int id){ return userService.getUserById(id); } @RequestMapping("/all") public List<User> findAllUser(){ return userService.getAllUser(); }}
UserService类
package org.cyxl.service;import org.cyxl.model.User;import org.cyxl.repository.UserRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;/** * Created by jeff on 15/10/23. */@Servicepublic class UserService { @Autowired UserRepository userRepository; public User getUserById(int id){ return userRepository.getUserById(id); } public List<User> getAllUser(){ return userRepository.getAllUser(); }}
UserRepository类
package org.cyxl.repository;import org.cyxl.model.User;import org.springframework.stereotype.Repository;import javax.jws.soap.SOAPBinding;import java.util.ArrayList;import java.util.List;/** * Created by jeff on 15/10/23. */@Repositorypublic class UserRepository { //模仿数据 private static List<User> users = new ArrayList<User>(); static { //初始化User数据 for (int i=0;i<10;i++){ User user = new User(); user.setId(i); user.setEmail("email" + i); user.setPassword("password" + i); users.add(user); } } public User getUserById(int id){ for (User user : users){ if(user.getId() == id){ return user; } } return null; } public List<User> getAllUser(){ return users; }}
此处没有用数据库,采用模拟数据
当前访问http://localhost:8080/user/2的结果是
{"id":2,"email":"email2","password":"password2"}
访问http://localhost:8080/user/all的结果是
[{"id":0,"email":"email0","password":"password0"},{"id":1,"email":"email1","password":"password1"},{"id":2,"email":"email2","password":"password2"},{"id":3,"email":"email3","password":"password3"},{"id":4,"email":"email4","password":"password4"},{"id":5,"email":"email5","password":"password5"},{"id":6,"email":"email6","password":"password6"},{"id":7,"email":"email7","password":"password7"},{"id":8,"email":"email8","password":"password8"},{"id":9,"email":"email9","password":"password9"}]
3)数据加密及过滤
实现自定义注解SerializedField
package org.cyxl.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by jeff on 15/10/23. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface SerializedField { /** * 需要返回的字段 * @return */ String[] includes() default {}; /** * 需要去除的字段 * @return */ String[] excludes() default {}; /** * 数据是否需要加密 * @return */ boolean encode() default true;}
实现ResponseBodyAdvice接口的MyResponseBodyAdvice
package org.cyxl;import org.cyxl.annotation.SerializedField;import org.cyxl.util.Helper;import org.springframework.core.MethodParameter;import org.springframework.core.annotation.Order;import org.springframework.http.MediaType;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.lang.reflect.Field;import java.util.*;/** * Created by jeff on 15/10/23. */@Order(1)@ControllerAdvice(basePackages = "org.cyxl.controller")public class MyResponseBodyAdvice implements ResponseBodyAdvice { //包含项 private String[] includes = {}; //排除项 private String[] excludes = {}; //是否加密 private boolean encode = true; @Override public boolean supports(MethodParameter methodParameter, Class aClass) { //这里可以根据自己的需求 return true; } @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //重新初始化为默认值 includes = new String[]{}; excludes = new String[]{}; encode = true; //判断返回的对象是单个对象,还是list,活着是map if(o==null){ return null; } if(methodParameter.getMethod().isAnnotationPresent(SerializedField.class)){ //获取注解配置的包含和去除字段 SerializedField serializedField = methodParameter.getMethodAnnotation(SerializedField.class); includes = serializedField.includes(); excludes = serializedField.excludes(); //是否加密 encode = serializedField.encode(); } Object retObj = null; if (o instanceof List){ //List List list = (List)o; retObj = handleList(list); }else{ //Single Object retObj = handleSingleObject(o); } return retObj; } /** * 处理返回值是单个enity对象 * * @param o * @return */ private Object handleSingleObject(Object o){ Map<String,Object> map = new HashMap<String, Object>(); Field[] fields = o.getClass().getDeclaredFields(); for (Field field:fields){ //如果未配置表示全部的都返回 if(includes.length==0 && excludes.length==0){ String newVal = getNewVal(o, field); map.put(field.getName(), newVal); }else if(includes.length>0){ //有限考虑包含字段 if(Helper.isStringInArray(field.getName(), includes)){ String newVal = getNewVal(o, field); map.put(field.getName(), newVal); } }else{ //去除字段 if(excludes.length>0){ if(!Helper.isStringInArray(field.getName(), excludes)){ String newVal = getNewVal(o, field); map.put(field.getName(), newVal); } } } } return map; } /** * 处理返回值是列表 * * @param list * @return */ private List handleList(List list){ List retList = new ArrayList(); for (Object o:list){ Map map = (Map) handleSingleObject(o); retList.add(map); } return retList; } /** * 获取加密后的新值 * * @param o * @param field * @return */ private String getNewVal(Object o, Field field){ String newVal = ""; try { field.setAccessible(true); Object val = field.get(o); if(val!=null){ if(encode){ newVal = Helper.encode(val.toString()); }else{ newVal = val.toString(); } } } catch (IllegalAccessException e) { e.printStackTrace(); } return newVal; }}
在beforeBodyWrite方法中,我们对拦截的数据根据配置文件进行是否加密和字段过滤。在类上面的注解Order是指定这个拦截器(切确的说是切入点,我们姑且叫做拦截器)的执行优先顺序,ControllerAdvice中的basePackages是指定哪些类需要使用该拦截器,这个很重要。
代码中用到两个工具类Helper和DesUtil这里也贴一下代码
Helper
package org.cyxl.util;import javax.crypto.BadPaddingException;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;/** * Created by jeff on 15/10/23. */public class Helper { private static String key = "wow!@#$%"; public static boolean isStringInArray(String str, String[] array){ for (String val:array){ if(str.equals(val)){ return true; } } return false; } public static String encode(String str){ String enStr = ""; try { enStr = DesUtil.encrypt(str, key); } catch (Exception e) { e.printStackTrace(); } return enStr; } public static String decode(String str) { String deStr = ""; try { deStr = DesUtil.decrypt(str, key); } catch (Exception e) { e.printStackTrace(); } return deStr; }}
DesUtil
package org.cyxl.util;import java.io.IOException;import java.security.SecureRandom;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder;public class DesUtil { private final static String DES = "DES"; public static void main(String[] args) throws Exception { String data = "123 456"; String key = "wow!@#$%"; System.err.println(encrypt(data, key)); System.err.println(decrypt(encrypt(data, key), key)); } /** * Description 根据键值进行加密 * @param data * @param key 加密键byte数组 * @return * @throws Exception */ public static String encrypt(String data, String key) throws Exception { byte[] bt = encrypt(data.getBytes(), key.getBytes()); String strs = new BASE64Encoder().encode(bt); return strs; } /** * Description 根据键值进行解密 * @param data * @param key 加密键byte数组 * @return * @throws IOException * @throws Exception */ public static String decrypt(String data, String key) throws IOException, Exception { if (data == null) return null; BASE64Decoder decoder = new BASE64Decoder(); byte[] buf = decoder.decodeBuffer(data); byte[] bt = decrypt(buf,key.getBytes()); return new String(bt); } /** * Description 根据键值进行加密 * @param data * @param key 加密键byte数组 * @return * @throws Exception */ private static byte[] encrypt(byte[] data, byte[] key) throws Exception { // 生成一个可信任的随机数源 SecureRandom sr = new SecureRandom(); // 从原始密钥数据创建DESKeySpec对象 DESKeySpec dks = new DESKeySpec(key); // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES); SecretKey securekey = keyFactory.generateSecret(dks); // Cipher对象实际完成加密操作 Cipher cipher = Cipher.getInstance(DES); // 用密钥初始化Cipher对象 cipher.init(Cipher.ENCRYPT_MODE, securekey, sr); return cipher.doFinal(data); } /** * Description 根据键值进行解密 * @param data * @param key 加密键byte数组 * @return * @throws Exception */ private static byte[] decrypt(byte[] data, byte[] key) throws Exception { // 生成一个可信任的随机数源 SecureRandom sr = new SecureRandom(); // 从原始密钥数据创建DESKeySpec对象 DESKeySpec dks = new DESKeySpec(key); // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES); SecretKey securekey = keyFactory.generateSecret(dks); // Cipher对象实际完成解密操作 Cipher cipher = Cipher.getInstance(DES); // 用密钥初始化Cipher对象 cipher.init(Cipher.DECRYPT_MODE, securekey, sr); return cipher.doFinal(data); }}
这里采用DES算法进行加密,可以设置自己的加密算法
接下来看看我们如何使用自定义注解和配置过滤字段,在UserController中的两个API我们的变化为
@RequestMapping("/{id}") @SerializedField(includes = {"id", "email"}, encode = false) public User findUserById(@PathVariable("id")int id){ return userService.getUserById(id); } @RequestMapping("/all") @SerializedField(excludes = {"id","password"}) public List<User> findAllUser(){ return userService.getAllUser(); }
查找单个user对象的api我们配置了应该包含id和email两个字段并不加密,访问http://localhost:8080/user/1得到的结果为
{"id":"1","email":"email1"}
可以看出结果中只有配置的两个字段id和email。
在查找所有user对象api中我们配置了去除id和password两个字段,加密使用默认的配置true,访问http://localhost:8080/user/all得到的结果为
[{"email":"VF+mPHf1EuI="},{"email":"EOJMFSFgsps="},{"email":"f4R+0CfsxMw="},{"email":"Yc9Q0i1HgII="},{"email":"/LByd7J+cF0="},{"email":"sZgJ3ylyxg0="},{"email":"hjYKo1ceg6U="},{"email":"l3TMFt0j+Kw="},{"email":"lkQICJp377U="},{"email":"/eSKhBhKP8w="}]
可以看出返回的数据中只有user的email字段,并且数据已经经过加密
最后来一张整体的文件结构图
所有代码均已在文中出现,包括一些注释
源代码地址: git://code.csdn.net/CYXLZZS/sprint-boot-responsebodyadvice.git
- Spring Boot应用之数据加密以及字段过滤
- Spring Boot应用之数据加密以及字段过滤
- Spring Boot 数据响应之数据加密以及字段过滤
- spring boot实战之XSS过滤
- Spring Boot学习(七)之Web应用使用Spring-data-jpa让数据访问
- Spring Boot:简化Spring应用初始搭建以及开发过程
- spring boot新手教程之使用FastJson解析JSON数据以及解决返回中文乱码问题
- spring-boot 之 使用Admin监控应用
- spring-boot 之 使用Admin监控应用
- spring-boot 之 使用Admin监控应用
- spring-boot 之 使用Admin监控应用
- Spring Boot探路者之应用配置
- spring-boot 之 使用Admin监控应用
- 3.Spring boot之返回json数据
- spring boot起步之返回json数据
- Spring Boot 之json数据解析
- Spring Boot之返回JSON数据
- Hibernate字段数据加密
- 在Servlet中获取Spring的指定bean
- 利用UIWebView打造一个炫酷的视频背景视图(OC & Swift)
- 安卓异步任务类AsyncTask——突出一个简单、好用
- nodejs微信开发--调用微信JS SDK
- 深度优先实现拓扑排序--java
- Spring Boot应用之数据加密以及字段过滤
- swift和oc单例创建方式
- Android API Level最新版本到6.0Platform Codenames, Versions, API Levels, and NDK Releases
- 为什么农民宁可被拘留也要烧秸秆?对烧秸秆农民拘留、罚款,非解决问题之道
- makefile里PHONY的相关介绍
- 中央文献重要术语译文发布
- Sql Server 数据分页
- 第七周 项目6 停车场模拟
- Java&Android开源库代码剖析】のandroid-async-http(如何设计一个优雅的Android网络请求框架,同...