sso组件(单点登录)
一、准备工作
1.确认动态域名
请首先确认你从得助平台获取的动态域名,得助平台提供的动态域名的格式形如: https://xxx.dezhuyun.com 。 本文档后续章节的例子中使用的动态域名为https://{您的专属域名}。
2.sso配置(获取鉴权Token)
1)请使用主账号登录得助后台,进入SSO配置页面,路径:设置-->系统对接-->第三方账号登录,填写相关的SSO配置。如下图:
登录URL:填写第三方平台的登录页地址;
默认部门:选择一个部门(得助系统中不存在的账号首次sso登录成功后用户在得助后台的部门);
默认角色:选择一个角色(得助系统中不存在的账号首次sso登录成功后用户在得助后台的角色);
退出URL:在得助后台退出后要跳转的URL;
2)填写完成并点击“保存”后,配置页面将显示由得助后台系统为你生成的鉴权token,请妥善保管该token,后续接入开发会使用该token来加密登录信息。
3.sso整体流程(适用于页面访问单点登录,不适应于单独拨号盘对接)
用户访问得助域名
-->页面自动跳转到第三方平台“登录URL”
-->用户填写第三方平台的用户名密码进行登录
-->第三方平台后台调用sso登录接口获取authorization(在header中),返回给第三方平台前端页面
-->第三方平台前端页面使用window.location.href方式跳转到得助平台,例如: window.localtion.href = 'https://{您的专属域名}?token=' + res.authorization
二、开发步骤
此处假设你已经完成“准备工作”一节中所有的要求,并已准备了一个Web系统用于开发。
1.单点登录接口调用
注意:为了您系统的安全性,请使用后台系统对接调用(应避免使用js从浏览器端直接调用本接口)。
你的Web页面需要通过调用得助平台提供的SSO登录接口,来完成SSO登录功能,以下是SSO登录接口说明:
接口地址 | https://{您的专属域名}/basic/user/sso/login | 接口地址的域名部分【mydomain】需要填写你从得助平台获取的动态域名 |
---|---|---|
方法 | POST | 【注意参数是query参数(非body)】 |
接口参数 (query参数) | data | 登录数据的加密字符串。data参数对应的值是对一个Json字符串使用rsa算法加密后生成的字符串(该json字符串字段见下方表格)。rsa使用的加密密钥,就是准备工作一节中获取到的鉴权token,为保证鉴权token不被窃取,建议在你的web后台实现接口,完成加密过程。 |
接口参数 (query参数) | timestamp | 当前时间戳(注意必须与data参数中的timestamp的值相同) |
data参数字段如下:
字段code | 是否必填 | 描述 |
---|---|---|
thirdpartAccount | 是 | 你使用的Web系统的登陆账号唯一标识(若没有,可使用手机号或邮箱) |
是 | 邮箱 | |
phone | 是 | 手机号 |
empno | 是 | 工号(若没有,可同手机号) |
timestamp | 是 | 当前时间戳,注意必须与接口query参数中的timestamp的值相同 |
注意:data和timestamp参数登录成功一次以后,就不能再使用了,后续重新登录需要使用新的timestamp和data;
接口调用成功后,会以json格式返回登陆信息。返回的header中含有字段名为authorization的参数。
2.登录成功后跳转页面(适用于页面访问单点登录,不适应于单独拨号盘对接)
在获取到authorization参数后,即可通过href链接的方式直接跳转到得助平台,跳转链接需增加token参数, 值就是返回Json中authorization字段的值,跳转链接生成示例:
window.localtion.href = 'https://{您的专属域名}?token=' + res.authorization
此时,由于之前已经通过SSO登录接口登录了得助平台,跳转完成后就会以已登录的状态进入得助平台管理界面,SSO登录成功!
3.单点登录接口调用Demo及工具类
鉴权Token:即示例代码中的publicKey, 详见:sso配置(获取鉴权Token)
鉴权token属于敏感数据,因此强烈建议你避免在前端js中使用鉴权token完成登录数据的加密,更好的方式是将鉴权token安全存储在你的web后台能访问的位置,并在你的web后台中实现一个登录数据加密接口来完成数据加密。 该接口只完成RSA加密,RSA是公开的标准加密算法,因此具体的实现由你web后台采用的技术框架而定,如在开发过程中遇到阻碍,可直接联系得助平台获取技术支持。
完整demo路径:https://open-docs.dezhuyun.com/src/dz-openapi-demo.zip
data参数加密demo:
long timestamp = System.currentTimeMillis();
JSONObject jsonObject = new JSONObject();
jsonObject.put("email","hucpoo@163.com");
jsonObject.put("phone","17511698701");
jsonObject.put("empno","000001");
jsonObject.put("thirdpartAccount","hucpoo@163.com");
jsonObject.put("timestamp",timestamp);
// 页面上的token
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDidXrjGBioBdTrdPqRm6SHdpoQ7NREZrkMNBF5NZfPaDijF+bg2OJKIOFaOJ1P0fcc/vR24yB/enr6/jMs/atnLPQHbkkXwGkYRfHqcG/DfoMRnizJCbYCK4vyJy/FcQiwJI19EwX9VjKtdqYKQe79Mcy/ZhyXCoZMoi87Kfn4BwIDAQAB";
String result = RSAUtil.rsaEncrypt(jsonObject.toString(), publicKey);
RestTemplate restTemplate = new RestTemplate();
String url = "https://gateway-worldtedu.51ima.com/basic/user/sso/login";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("data", result);
params.add("timestamp", String.valueOf(timestamp));
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<JSONObject> jsonObjectResponseEntity = restTemplate.postForEntity(url, request, JSONObject.class);
System.out.println(jsonObjectResponseEntity.getStatusCode());
System.out.println(jsonObjectResponseEntity.getHeaders());
RSA加密类代码如下:
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
public class RSAUtil {
/**
* 密钥长度(bit)
*/
// FIXME 按公司规范,秘钥长度需要大于2048
public static final int KEY_LENGTH = 1024;
public static final int MAX_ENCRYPT_BLOCK = 117;
/**
* <p>
* 单次解密最大密文长度,这里仅仅指1024bit 长度密钥
* </p>
*
* @see #MAX_ENCRYPT_BLOCK
*/
public static final int MAX_DECRYPT_BLOCK = 128;
/**
* 加密算法
*/
public static final String ALGORITHM_RSA = "RSA";
/**
* 算法/模式/填充
*/
public static final String CIPHER_TRANSFORMATION_RSA = "RSA/ECB/PKCS1Padding";
/**
* 签名算法
*/
public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
/**
* UTF-8字符集
**/
public static final String CHARSET_UTF8 = "UTF-8";
/**
* GBK字符集
**/
public static final String CHARSET_GBK = "GBK";
public static final String CHARSET = CHARSET_UTF8;
/**
* 得到公钥
*
* @param key 密钥字符串(经过base64编码)
* @param charset
* @throws Exception
*/
public static PublicKey getPublicKey(String key, String charset)
throws Exception {
byte[] keyBytes = Base64.decodeBase64(key.getBytes(charset));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
/**
* 得到私钥
*
* @param key 密钥字符串(经过base64编码)
* @param charset
* @throws Exception
*/
public static PrivateKey getPrivateKey(String key, String charset)
throws Exception {
byte[] keyBytes;
keyBytes = Base64.decodeBase64(key.getBytes(charset));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
/**
* 得到密钥字符串(经过base64编码)
*
* @return
*/
public static String getKeyString(Key key) throws Exception {
byte[] keyBytes = key.getEncoded();
String s = new String(Base64.encodeBase64(keyBytes), CHARSET);
return s;
}
/**
* 公钥加密
*
* @param content 待加密内容
* @param publicKey 公钥
* @param charset 字符集,如UTF-8, GBK, GB2312
* @return 密文内容
* @throws Exception
*/
public static String rsaEncrypt(String content, String publicKey,
String charset) throws Exception {
try {
PublicKey pubKey = getPublicKey(publicKey, charset);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION_RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = StringUtils.isEmpty(charset) ? content.getBytes()
: content.getBytes(charset);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = Base64.encodeBase64(out.toByteArray());
out.close();
return StringUtils.isEmpty(charset) ? new String(encryptedData)
: new String(encryptedData, charset);
} catch (Exception e) {
throw new Exception(
"error occured in rsaEncrypt: EncryptContent = " + content
+ ",charset = " + charset, e);
}
}
/**
* 公钥加密
*
* @param content 待加密内容
* @param publicKey 公钥
* @return 密文内容
* @throws Exception
*/
public static String rsaEncrypt(String content, String publicKey)
throws Exception {
return rsaEncrypt(content, publicKey, "utf8");
}
/**
* 私钥解密
*
* @param content 待解密内容
* @param privateKey 私钥
* @param charset 字符集,如UTF-8, GBK, GB2312
* @return 明文内容
* @throws Exception
*/
public static String rsaDecrypt(String content, String privateKey,
String charset) throws Exception {
try {
PrivateKey priKey = getPrivateKey(privateKey, charset);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION_RSA);
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] encryptedData = StringUtils.isEmpty(charset) ? Base64
.decodeBase64(content.getBytes()) : Base64
.decodeBase64(content.getBytes(charset));
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet,
MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen
- offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return StringUtils.isEmpty(charset) ? new String(decryptedData)
: new String(decryptedData, charset);
} catch (Exception e) {
throw new Exception("error occured in rsaDecrypt: EncodeContent = "
+ content + ",charset = " + charset, e);
}
}
/**
* 私钥解密
*
* @param content 待解密内容
* @param privateKey 私钥
* @return 明文内容
* @throws Exception
*/
public static String rsaDecrypt(String content, String privateKey)
throws Exception {
return rsaDecrypt(content, privateKey, "utf8");
}
/**
* 获得密钥对
*
* @return
* @throws NoSuchAlgorithmException KeyPair
* @Title creatKeyPair
* @Description TODO
*/
public static KeyPair creatKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 密钥位数
keyPairGen.initialize(KEY_LENGTH);
// 密钥对
KeyPair keyPair = keyPairGen.generateKeyPair();
return keyPair;
}
}