1.jsonp介绍:
Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。简单说是利用<script>标签没有跨域限制的“漏洞”来达到与第三方通讯的目的。
当项目的前后端分离,部署在不同的服务器上就要面临跨域问题,这时可以用jsonp来实现跨域数据请求。
2.jsonp使用:
前端jquery ajax请求方式:
var data={
param1:param1 //参数1 } $.ajax({ type : "get", async:false, url : "****/test.json",//后端请求地址 dataType : "jsonp", data:data, jsonp: "callbackparam",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback) success : function(json){ alert(json.result[0].personName); } });后端代码(springMvc框架):
@ResponseBody
@RequestMapping(value="test.json") public JSONPObject test(String param1,String callbackparam) throws Exception { ModelMap model = new ModelMap(); List<PersonInfo> personList = new ArrayList<PersonInfo>(); PersonInfo person = new PersonInfo(); person.setPersonName("李四"); person.setBornLocation("火星"); personList.add(person); person = new PersonInfo(); person.setPersonName("张三"); person.setBornLocation("南极"); model.addAttribute("result", personList); JSONPObject resultFinal = new JSONPObject(callbackparam, model); return resultFinal; }3.数据传输加密:
由于jsonp只支持get方式发送请求,即使在ajax中设置为post,jquery也会自动转为get。所以数据传输缺少安全性,上网了解了一下前辈们的经验,总结了以下AES+RSA加密实现方式:
(1) AES 加密
需要引入相关jar包,(maven)pom配置如下
<dependency>
<groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.57</version> </dependency>(2)前端工具类代码(发布时最好做压缩混淆,注:代码非原创来源网上)
/**
* 加密解密 */ /** RSA加密用 获取RSQ公钥 */ function bodyRSA(){ /** 1024位的key参数写130,2014位的key参数写260 */ setMaxDigits(130); /** ajax 调用后台方法,取回公钥 */ var keyR ; $.ajax({ url: "Key/pk",//请求后台的url,本例是springMVC框架 type: "post", cache: false, async : false, dataType: "text", success: function (data) { keyR = data; }, error:function (XMLHttpRequest, textStatus, errorThrown) { alert("与服务器连接失败!"); } }); /** RSAKeyPair 函数三个参数:加密指数、解密指数、系数 */ return new RSAKeyPair("10001","",keyR); } /** AES加密用 随机生成key和iv */ function randomString() { var chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var length= chars.length; var pwd=''; for(var i = 0; i < 16 ;i++){ pwd += chars.charAt(Math.floor(Math.random() * length)); } return pwd; } /** * AES加密 * data * key * iv * @returns */ function getAesString(data,key,iv){ var key = CryptoJS.enc.Utf8.parse(key); var iv = CryptoJS.enc.Utf8.parse(iv); var encrypted = CryptoJS.AES.encrypt(data,key, { iv:iv, mode:CryptoJS.mode.CBC, padding:CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } /** * AES解密 * @param encrypted * @param key * @param iv * @returns */ function getDAesString(encrypted,key,iv){ var key = CryptoJS.enc.Utf8.parse(key); var iv = CryptoJS.enc.Utf8.parse(iv); var decrypted = CryptoJS.AES.decrypt(encrypted,key, { iv:iv, mode:CryptoJS.mode.CBC, padding:CryptoJS.pad.Pkcs7 }); return decodeURIComponent(decrypted.toString(CryptoJS.enc.Utf8)).replace("+", " "); }后端获取RSA公钥代码(注:来源网上)
@Controller
@RequestMapping("Key") public class PublicKeyController { /** * 获取RSA密钥文件中的公钥 * @return String类型 * @throws Exception */ @RequestMapping("/pk") @ResponseBody public String getPublicKey() throws Exception{ /** 实例化加密解密工具类*/ EncryptionDecryption ed = new EncryptionDecryption(); // ed.generateKeyPair(); //首次运行,需要放开此段代码,来生成公钥及私钥 return ed.getPublicKey(); } }(3)后端工具类代码(注:来源网上):
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; /** * rsa aes 加密解密工具类 * Title: EncryptionDecryption * Company: djzh * @author hanlin * @date 2017年1月17日 上午11:02:50 */ public class EncryptionDecryption { /** 密钥文件存储位置 */ private static String RSAKeyStore = "C:/RSAKey.txt";//在这个位置放这个文件 /** * 日志记录器 */ public static Logger logger = Logger.getLogger(EncryptionDecryption.class); /** * AES加密 * @param content 明文 * @param keyBytes 秘钥 * @param iv 偏移量 * @return */ public static String AES_CBC_Encrypt(String content, byte[] keyBytes, byte[] iv){ try{ SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); content = URLEncoder.encode(content,"UTF-8"); //用url编码 byte[] result=cipher.doFinal(content.getBytes()); //加密 return new String(Base64.encodeBase64(result),"UTF-8"); }catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } return null; } /** * AES解密 * @param content 密文 * @param keyBytes 秘钥 * @param iv 偏移量 * @return */ public static String AES_CBC_Decrypt(String content, byte[] keyBytes, byte[] iv){ try{ content = content.replaceAll(" ", "+"); byte[] decryptBaseData=Base64.decodeBase64(content.getBytes("utf-8")); SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); byte[] result=cipher.doFinal(decryptBaseData); return URLDecoder.decode(new String(result),"utf-8"); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } return null; } /** * 字符串转为 byte[] * @param hexString * @return */ public static byte[] hexStringToBytes(String hexString) { if (hexString == null || hexString.equals("")) { return null; } hexString = hexString.toUpperCase(); int length = hexString.length() / 2; char[] hexChars = hexString.toCharArray(); byte[] d = new byte[length]; for (int i = 0; i < length; i++) { int pos = i * 2; d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); } return d; } /** * Convert char to byte * @param c char * @return byte */ private static byte charToByte(char c) { return (byte) "0123456789ABCDEF".indexOf(c); } /** * 解密由RSA加密的AES的key 和 iv * @param para * @return * @throws Exception */ public static byte[] getValue(String param) throws Exception{ byte[] trueValue = null; try { if(!param.equals("") && param != null){ byte[] KeyB = hexStringToBytes(param); KeyB = decrypt(getKeyPair().getPrivate(),KeyB); StringBuffer sbKey = new StringBuffer(); sbKey.append(new String(KeyB)); param = sbKey.reverse().toString(); trueValue = URLDecoder.decode(param,"UTF-8").getBytes(); } } catch (Exception e) { //重要参数值 logger.error("传入参数:" + "param: " + param); //异常说明 logger.error("解密由RSA加密的AES的key 和 iv 失败,可能前台传入的aKey或者aIv为空"); e.printStackTrace(); } return trueValue; } /** * 获取密钥文件中的公钥 * @return */ public String getPublicKey(){ Object publicKey = null; String publicKEY = null; try { publicKey = getKeyPair().getPublic(); publicKEY = (String) publicKey.toString().subSequence(37, 293); } catch (Exception e) { e.printStackTrace(); } return publicKEY; } /** * RSA 生成密钥对 * @return * @throws Exception */ public static KeyPair generateKeyPair() throws Exception { try { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); final int KEY_SIZE = 1024; keyPairGen.initialize(KEY_SIZE, new SecureRandom()); KeyPair keyPair = keyPairGen.generateKeyPair(); FileOutputStream fos = new FileOutputStream(RSAKeyStore); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(keyPair); oos.close(); fos.close(); return keyPair; } catch (Exception e) { throw new Exception(e.getMessage()); } } /** * 获取密钥对 * @return * @throws Exception */ public static KeyPair getKeyPair() throws Exception { FileInputStream fis = new FileInputStream(RSAKeyStore); ObjectInputStream oos = new ObjectInputStream(fis); KeyPair kp = (KeyPair) oos.readObject(); oos.close(); fis.close(); return kp; } /** * RSA解密,获取AES的key和iv时调用 * @param pk * @param raw * @return * @throws Exception */ @SuppressWarnings("static-access") private static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception { try { Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); cipher.init(cipher.DECRYPT_MODE, pk); int blockSize = cipher.getBlockSize(); ByteArrayOutputStream bout = new ByteArrayOutputStream(64); int j = 0; while (raw.length - j * blockSize > 0) { bout.write(cipher.doFinal(raw, j * blockSize, blockSize)); j++; } return bout.toByteArray(); } catch (Exception e) { throw new Exception(e.getMessage()); } } }至此准备工作完成,如下为加密方式调用的代码
(4)加密方式调用
前端ajax 加密方式请求代码:
var keyRSA = bodyRSA(); //生成RSA加密用的key
var key = randomString();//随机生成AES的key 和 iv var iv = randomString(); var aKey = encryptedString(keyRSA, encodeURIComponent(key)); //RSA加密AES的key var aIv = encryptedString(keyRSA, encodeURIComponent(iv)); //RSA加密AES的iv var param1="张三";//参数1 var param1_ = getAesString(encodeURIComponent(param1),key,iv); //AES加密参数内容1 //筛选条件的参数 var data={ param1:param1_, //参数1 aKey:aKey, aIv:aIv } $.ajax({ type : "GET", async: false, url:"***/test.json",//请求的url,本例为springMVC框架 dataType:'JSONP', data:data, jsonp:'callbackparam', success:function(json){ alert(json.result); var decryptedStr = getDAesString(json.result,key,iv);//解密 var resultObj = JSON.parse(decryptedStr); alert(resultObj[0].personName); } });后端代码:
@ResponseBody
@RequestMapping(value="test.json") public JSONPObject testRes(String param1,String aKey,String aIv,String callbackparam) throws Exception { ModelMap model = new ModelMap(); EncryptionDecryption encryptionDecryption = new EncryptionDecryption(); String key = encryptionDecryption.getValue(aKey).toString(); String iv = encryptionDecryption.getValue(aIv).toString(); byte[] keyBytes = encryptionDecryption.getValue(aKey); byte[] ivBytes = encryptionDecryption.getValue(aIv); param1 = encryptionDecryption.AES_CBC_Decrypt(param1, keyBytes, ivBytes); System.out.println("key:"+key+"\n"); System.out.println("iv:"+iv+"\n"); System.out.println("param1:"+param1+"\n"); List<PersonInfo> personList = new ArrayList<PersonInfo>(); PersonInfo person = new PersonInfo(); person.setPersonName("李四"); person.setBornLocation("火星"); personList.add(person); person = new PersonInfo(); person.setPersonName("张三"); person.setBornLocation("南极"); String result = JsonUtils.toJson(personList);//把list转为json格式字符串 System.out.println("&&&&&&&&&&&&&&"+result+"\n"); result = encryptionDecryption.AES_CBC_Encrypt(result, keyBytes, ivBytes); model.addAttribute("result", result); JSONPObject resultFinal = new JSONPObject(callbackparam, model); return resultFinal; }JsonUtils代码:
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.ObjectMapper; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;public abstract class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
public static String toJson(Object bean) {
StringWriter stringWriter = new StringWriter(); try { JsonGenerator jsonGenerator = objectMapper.getJsonFactory() .createJsonGenerator(stringWriter); objectMapper.writeValue(jsonGenerator, bean); return stringWriter.toString(); } catch (IOException e) { } return "{}"; }}
调用结束,前端正常获取加密数据并进行解密显示。
(5)总结说明:
参数,及返回结果使用AES方式加密
AES中用到的key和iv在传输过程中用RSA加密
后端服务器需要存储一份RSA生成的公钥和私钥,生成的代码在工具类中,公钥私钥生成一次即可,如果需要更新先清空密钥文件内容,然后执行生成代码。
需要下载引入的js文件(rsa及aes相关):无法上传附件,就把文件名贴出来吧
<script language="javascript" type="text/javascript" src="statics/js/res/core-min.js"></script>
<script language="javascript" type="text/javascript" src="statics/js/res/lib-typedarrays-min.js"></script> <script language="javascript" type="text/javascript" src="statics/js/res/aes.js"></script> <script language="javascript" type="text/javascript" src="statics/js/res/RSA.js"></script> <script language="javascript" type="text/javascript" src="statics/js/res/BigInt.js"></script> <script language="javascript" type="text/javascript" src="statics/js/res/Barrett.js"></script> <script language="javascript" type="text/javascript" src="statics/js/json2.js"></script>理解说明不对的地方欢迎批评指正