平台第三方应用集成单点登录解决方案
- 使用场景
基于统一用户平台实现多应用集成单点登录,统一入口登录后用户实现共享,实现多系统认证登录,权限由各业务系统内部实现。
- 实现原理
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。一个JWT实际上就是一个字符串,它由三部分组成,头部(header)、载荷(playload)与签名(signature)。
1)目标应用获取到token后需要对其进行解密,得到JWT头、有效负荷,按照约定算法进行编码加密形成验签,再同签名进行对比以判断数据的完整性和一致性。
2)验证通过后解码有效载荷内容,获取有效期属性判断该token是否超期失效。
3)验证token在有效期内,获取到统一用户的登录ID,并进行自己系统内的用户验证和权限判断实现登录或者跳转到业务界面。
- 单点登录使用方式
单点登录的目标应用访问地址:
http://www.xxx.gov.cn/xxx/single.do?token= ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKTlJEVklaWGdpZlE9PS5leUpsZUhBaU9pSXlNREl3TFRBeExURTJJREV6T2pNNE9qTTFJaXdpZFhObGNtbGtJam9pYkc5bmFXNXVZVzFsSWl3aWRYTmxjbTVoYldVaU9pTHBxNWptaUpEcGxJc2lmUT09Ljc0QkIyNkFGQjVBNjZBNzI2NENERUM4MDVFMDA5RDUy
JAVA:
方法一:需要导入commons-lang-2.5.0.jar jackson-all-1.9.11.jar sso-decoder-1.0.0.jar
如果应用中已经存在对应jar包或者更高版本则可以不用导入。
代码示例:
import com.boshan.sso.validation.TokenDecoder;
TokenDecoder tokenDecoder = new TokenDecoder();
String result = tokenDecoder.checkUserToken(token);
result参数说明 | {“issuccess”:”true”, ”userid”:”admin”, “message”:””} | |
参数名称 | 参数值 | 说明 |
issuccess | true/false | 验证通过则为true |
userid | 用户登录ID | 验证失败值为空 |
message | 单点登录验证消息 | 验证通过值为空 |
方法二:对token进行Base64解码,解码后的字符串由三部分组成.
如:头部header.载荷playload.签名signature
eyJ0eXAiOiJKV1QiLCJhbGciOiJNRDVIZXgifQ==.eyJleHAiOiIyMDIwLTAxLTE2IDEzOjM4OjM1IiwidXNlcmlkIjoibG9naW5uYW1lIiwidXNlcm5hbWUiOiLpq5jmiJDplIsifQ==.74BB26AFB5A66A7264CDEC805E009D52
对头部header进行Base64解码后获得字符串 {“typ”:”jwt”, ” alg”:” MD5Hex” } 得到算法MD5Hex。
联系方案提供方获取截长数值SPLIT和秘钥SECRETKEY ①。
根据SPLIT大小对头部header截取字符串前几位作为一个字符串值②。
根据SPLIT大小对载荷playload截取字符串后几位作为一个字符串值③。
将①,②,③ 按照对应算法进行加密,将加密后的字符串与签名signature进行比对以便验证数据的完整性和一致性。
一致性验证通过后,对载荷playload字符串再进行Base64解码获取一个json字符串。
如:{“username”:”张三”, ”userid”:”admin”, “exp”:”2020-01-16 16:22:39”}
请根据exp字段与系统当前时间进行验证该token是否已经失效。
有效性验证通过后,可以直接获取userid字段值,然后在自己系统内验证用户有效性以及权限进行系统登录或者界面跳转。
public String getSign(String header, String paload){
String prifix = header.substring(0, Constants.SPLIT);
String suffix = paload.substring(paload.length() - Constants.SPLIT, paload.length());
String sign = "";
try {
String[] str = {prifix, Constants.SECRETKEY, suffix};
List<Byte> list = new ArrayList<Byte>();
for (String string : str) {
for(byte b : string.getBytes("UTF-8")) {
list.add(b);
}
}
Collections.sort(list);
sign = StringUtils.join(list.toArray(), ",");
sign = MD5Hex (sign, 32);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return sign;
}
private static String MD5Hex (String contentText,int length) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(contentText.getBytes());
byte bytes[] = messageDigest.digest();
int index;
StringBuffer data = new StringBuffer("");
for (int offset = 0; offset < bytes.length; offset++) {
index = bytes[offset];
if (index < 0) {
index += 256;
}
if (index < 16) {
data.append("0");
}
data.append(Integer.toHexString(index));
}
if (length == 32) {//32位加密
return data.toString().toUpperCase();
}else if (length == 16) {// 16位的加密
return data.toString().substring(8, 24).toUpperCase();
}else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
C#、PHP等其他编程语言请参看以上方法二的实现原理进行处理。