思路举例:
以QQ为例:pc端QQ界面中所有的第三方软件系统,以QQ邮箱举例,
QQ中成功登陆,从而点击QQ邮箱可以直接打开网页进入邮箱并无需登录
在这里就使用了单点登录,QQ邮箱就是QQ信任的第三方系统,双方达成协议,
双方需要把加密方式,和加密串达成一致。当然如果公司是中型企业,加密串后期
会有运维去和客户沟通。 接下来贴代码。
代码实例:
首先设置一个加密串,这里是在properties文件中定义,嫌麻烦可以直接在接口中定义静态私有属性
#秘钥
AtsecretKey=Activity0CoJUm6123456789
//引入秘钥
@Value("${AtsecretKey}")
private String AtsecretKey;
/**
* 根据请求的参数加密判断
* @param currTime
* @param idcardno
* @param type
* @param signature
* @return
*/
@RequestMapping("/token")
@ResponseBody
public ResultEntity checkToken(String currTime,String idcardno,String type,String signature ){
ResultEntity resultEntity=new ResultEntity();
Map<String,String> desMap=new HashMap<>();
desMap.put("currTime",currTime);
desMap.put("idcardno",idcardno);
desMap.put("type",type);
StringBuffer sb = new StringBuffer();
StringBuffer sbkey = new StringBuffer();
Set es = desMap.entrySet(); //所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
//空值不传递,不参与签名组串
if(null != v && !"".equals(v)) {
sb.append(v);
sbkey.append(v);
}
}
sbkey=sbkey.append(AtsecretKey);
System.out.println("字符串:"+sbkey.toString());
String md5Item = MD5Util.md5(sbkey.toString());
System.out.println("MD5加密值:"+md5Item);
System.out.println(sb.toString()+"sign="+md5Item);
//拼字符串后加密
if (md5Item.equals(signature)){
long nowTime=System.currentTimeMillis();
if (((nowTime - Long.parseLong(currTime))*1.0 /(1000 * 60))>20){
resultEntity.setState(HttpCode.FAILED);
resultEntity.setRetMessage("认证时间超时");
return resultEntity;
}
DESPlus desPlus=new DESPlus();
String strJson=JsonUtils.toJson(desMap);
String desMessage=desPlus.encrypt(strJson);
resultEntity.setState(HttpCode.SUCCESS);
resultEntity.setMessage("认证通过");
resultEntity.setResult(desMessage);
return resultEntity;
}
resultEntity.setState(HttpCode.FAILED);
resultEntity.setMessage("请求失败");
return resultEntity;
}
代码解释:
ResultEntity:工具类,用于封装json数据,返回页面响应请求
currTime:时间戳
idcardno:身份证号
type:设备类型手机或者pc
signature:加密串,
Map<String,String> desMap=new HashMap<>(); desMap.put("currTime",currTime); desMap.put("idcardno",idcardno); desMap.put("type",type);
将参数放入map中,方便接下来排序
StringBuffer sb = new StringBuffer(); StringBuffer sbkey = new StringBuffer(); Set es = desMap.entrySet(); //所有参与传参的参数按照accsii排序(升序) Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); Object v = entry.getValue(); //空值不传递,不参与签名组串 if(null != v && !"".equals(v)) { sb.append(v); sbkey.append(v); }
sbkey=sbkey.append(AtsecretKey);
System.out.println("字符串:"+sbkey.toString());
String md5Item = MD5Util.md5(sbkey.toString());
System.out.println("MD5加密值:"+md5Item);
System.out.println(sb.toString()+"sign="+md5Item);
这里需要用的accsii排序,accsii详细解释可以去查一下
通过迭代器迭代排序,并且清除空值,然后将值封装好key,value
另外在排序后的值后面加上加密串(AtsecretKey),生成加密签名
接着使用md5工具类加密参数
//拼字符串后加密 if (md5Item.equals(signature)){ long nowTime=System.currentTimeMillis(); if (((nowTime - Long.parseLong(currTime))*1.0 /(1000 * 60))>20){ resultEntity.setState(HttpCode.FAILED); resultEntity.setRetMessage("认证时间超时"); return resultEntity; } DESPlus desPlus=new DESPlus(); String strJson=JsonUtils.toJson(desMap); String desMessage=desPlus.encrypt(strJson); resultEntity.setState(HttpCode.SUCCESS); resultEntity.setMessage("认证通过"); resultEntity.setResult(desMessage); return resultEntity; } resultEntity.setState(HttpCode.FAILED); resultEntity.setMessage("请求失败"); return resultEntity;
这里将加密后得参数和第三方传的signature判断是否相同,如果相同证明是信任的第三方,
才可以执行下面的操作
拿到第三方发送的时间戳(currTime)并且获取当前的时间戳,设置时间戳过期时间,
这里是防止恶意攻击,不可能第二天还可以拿着前一天的请求来访问,
成功设置后在使用DES加密,因为des是可以解密的,将之前map的参数在一次用des加密,
转换成json数据,让后放进工具类返回请求,第三方拿到参数,再次带着参数来访问接口,
/** * 根据第三方的Token来处理单点登录 * @param request * @param session * @return */ @RequestMapping("/IntoLogin") public String intoLogin( HttpServletRequest request, HttpSession session,String token){ //拿到token解密 DESPlus desPlus=new DESPlus(); try { String decToken=desPlus.decrypt(token); Map<String,String>map=JsonUtils.fromJson(decToken,Map.class); ReaderEntity record = new ReaderEntity(); record.setCardno(map.get("cardno")); record.setIdcardno(map.get("idcardno")); String type=map.get("type"); String currTime=map.get("currTime"); ReaderSession readerSession =null; long nowTime=System.currentTimeMillis(); //判断过期时长,请求时间超过20分钟就无效 if (((nowTime - Long.parseLong(currTime))*1.0 /(1000 * 60))<20) { readerSession = readerService.login(record, request); } if(readerSession!=null) { session.setAttribute("readerInfo", readerSession); UnionUserSession unionUserSession = new UnionUserSession(); unionUserSession.setUserType(2); unionUserSession.setUserId(readerSession.getReader().getRecno().toString()); request.getSession().setAttribute(UnionUserSession.SESSION_ID, unionUserSession); //这里判断密钥类型是pc还是移动端,登录后将跳转不同页面 if (type.equals("0")){ return "/pc/index"; }else{ return "/mobile/activity/home"; } } }catch (Exception e) { return "/pc/index"; } return "/pc/index"; }
这里的代码不多做解释,应该能看得懂,其实这种单点登录思路要屡清楚
为了安全性参数一定是要加密的,特别是加密协议,密钥是双方知道的,第三方
请求的时候也是带着参数可已经加密的参数来求接口,如果我们拿着他们的参数排序
在加上密钥以后加密,发现加密后的字符串一致说明是我们信任的第三方,
这里单点登录要用两个接口,所以我们需要使用des加密后返回给他们再次请求,
一个接口负责判断是否第三方,另一个接口登录,
这里只提供接口供第三方访问。