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加密
* @param data
* @param key
* @param 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>
理解说明不对的地方欢迎批评指正