当单点登录不能满足外接系统对接要求时,取消拦截实行定点访问的思路。
在今年五月份的时候有一个系统需要对接我已上线系统,但是他有区别与以前的单点登录系统,其他系统可以免输入账户密码的情况下直接登录我系统,他是一个只有传输数据到我系统,而我系统也只是对他的传输数据进行处理,返回处理数据的一个模式,起初这个用单点登录很容易就可以解决,但是同时存在隐患,因为虽然我方给其他系统设置单点登录到我系统的某一页面或者只是返回一个数据结果,但是他们已经获取了单点登录的socket,可以利用这个socket进行对系统任意页面访问,这样就存在安全隐患。
于是思索了几天还是取消拦截,利用AES编码+jsonp的方式,进行验证访问比较安全可靠。于是就有了后来的模块开发,以及多系统对接。主要思路,去掉我方某一方法的访问拦截,和其他系统开发人员约定AES加密key,让对方拼接用户名加当前时间戳然后加密与请求参数一期发过来,我方获取对方传递过来的加密字符串后,解密,得到用户名和时间戳,让后匹配时间戳,范围根据自己系统情况设定,1-3分钟之内,都可以,时间戳匹配得当的情况下去匹配用户,用户也匹配好的情况下,支持访问某一接口。这样就有了三重防护:第一层AES加密,第二层,时间范围比对,第三层,用户比对。
模拟测试:
一、AES加密完成测试数据加密
package com.zx.ps.web.gzdb; import java.text.SimpleDateFormat; import java.util.Date; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; /** * @author : ckx * @date : 2017-9-18 下午3:23:20 */ public class AES { // 加密 public static String Encrypt(String sSrc, String sKey) throws Exception { if (sKey == null) { System.out.print("Key为空null"); return null; } // 判断Key是否为16位 if (sKey.length() != 16) { System.out.print("Key长度不是16位"); return null; } byte[] raw = sKey.getBytes("utf-8"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式" cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8")); return new Base64().encodeToString(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。 } // 解密 public static String Decrypt(String sSrc) throws Exception { try { String sKey = "SZZX-03110918-PS"; byte[] raw = sKey.getBytes("utf-8"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密 try { byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original,"utf-8"); return originalString; } catch (Exception e) { System.out.println(e.toString()); return null; } } catch (Exception ex) { System.out.println(ex.toString()); return null; } } public static void main(String[] args) throws Exception { //KEY:SZZX-03110918-PS String cKey = "SZZX-03110918-PS"; // 需要加密的字串:时间戳(精确到秒)+用户名 Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); //等待加密字符串:时间戳+用户名 String cSrc = sdf.format(date)+"wangzhenxun"; System.out.println(cSrc); // 得到加密字符串:AES.Encrypt(待加密字符串, 加密公钥); String enString = AES.Encrypt(cSrc, cKey); System.out.println("加密后superKey是:" + enString); // 解密 String DeString = AES.Decrypt(enString); System.out.println("解密后的字串是:" + DeString.substring(14,DeString.length())); System.out.println("CompareKey:"+CompareKey(DeString.subSequence(0, 14).toString())); } }
二、模拟http请求:
package com.zx.ps.web.gzdb; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map; public class ckx2Http { private static ckx2Http HttpRequest; /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { String result = ""; BufferedReader in = null; try { String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); System.out.println("#1:"+realUrl); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 建立实际的连接 connection.connect(); // 获取所有响应头字段 Map<String, List<String>> map = connection.getHeaderFields(); // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println("#2:"+key + "--->" + map.get(key)); } // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } /** * 向指定 URL 发送POST方法的请求 * * @param url * 发送请求的 URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送 POST 请求出现异常!"+e); e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally{ try{ if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch(IOException ex){ ex.printStackTrace(); } } return result; } public static void main(String[] args) { String sr=HttpRequest.sendPost("http://localhost:8080/***/***/se_zhcs_finish.pt", "task_sys_source=zhcs&superKey=SQK7aib/XHIxGUrJnR7Md0L6RtDrPmRTOw3W2xcFHTE="); System.out.println("#3"+sr); } }
三、我方的去拦截访问地址:
@RequestMapping("/se_zhcs_finish") @ResponseBody public void se_zhcs_finish(HttpServletRequest request,HttpServletResponse response) throws Exception{ Dmp dmp = this.getParamsAsDmp(); //获取加密参数,并利用封装Decrypt(string)解密方法解密参数 String superKey = Decrypt(dmp.getAsString("superKey")); if (superKey != null) { //获取解密字符串的时间戳 String comp = superKey.substring(0, 14); //获取解密字符串的用户名 String account = superKey.substring(14,superKey.length()); //1、比较时间范围,看是否是一个过期的加密串。2、在时间范围合适的情况下比较用户名,返回Boolean值,做访问钥匙。 boolean bo = CompareKey(comp,account); //通过验证的情况下可以操作或者得到数据。 if (bo) { response.setContentType("application/x-javascript"); response.setCharacterEncoding("UTF-8"); response.setHeader("P3P", "CP=CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"); try { PrintWriter out = response.getWriter(); List li = dao.getList("gzdb_control.se_zhcs_finish", dmp); String finishids = ""; for (int i = 0; i < li.size(); i++) { Map m = (Map) li.get(i); finishids = finishids + m.get("id") + ","; } if (!li.isEmpty()) { out.write("jsonpcallback({ \"ids\":\"" + finishids.subSequence(0, finishids.length()-1) + "\"});"); }else { out.write("jsonpcallback({ \"ids\":\"" + finishids + "\"});"); } //out.write("jsonpcallback({ \"ids\":\"" +taskid+ "\"});"); out.flush(); out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } //解密加密字符串 public String Decrypt(String sSrc) throws Exception { try { String sKey = "SZZX-03110918-PS"; byte[] raw = sKey.getBytes("utf-8"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密 try { byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original,"utf-8"); return originalString; } catch (Exception e) { //System.out.println(e.toString()); return null; } } catch (Exception ex) { //System.out.println(ex.toString()); return null; } } //比较解密后的字符串的封装方法 public boolean CompareKey(String comp,String account){ Date date = new Date(); SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); System.out.println("今天的日期:"+df.format(date)); System.out.println("十分钟前:" + df.format(new Date(date.getTime() - 10* 60 * 1000))); String str11 = df.format(date); String str33 = df.format(new Date(date.getTime() - 10* 60 * 1000)); //当前时间戳 long lg11 = new Long(str11); //十分钟前时间戳 long lg33 = new Long(str33); //解密时间戳 long lg22 = new Long(comp); boolean bo = false; System.out.println(lg11+"*"+lg22+"*"+lg33); //这里测试用的是当前时间前10分钟到当前时间均为有效 if (lg33<lg22 && lg22<=lg11) { Dmp dmp = this.getParamsAsDmp(); dmp.put("account", account); //比较用户名 int acc = dao.getList("gzdb_control.se_user_massage", dmp).size(); if (acc >= 1) { //修改访问标识 bo = true; } } //返回访问标识 return bo; }