技术文章

当前位置:首页>帮助手册>技术文章

使用鉴权配置实现外部系统与O2OA平台的单点登录

时间:2024-02-28   

  单点登录(Single Sign On),简称为 SSO,定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

O2OA系统默认提供两种单点登陆解决方案:加密认证方式和OAuth2认证方式。本文主要介绍加密认证方式。有关OAuth2认证方式请查看:https://www.o2oa.net/cms/tech/102.html

加密认证方式

加密认证方案是第三方系统通过生成加密的临时票据,然后使用这个临时票据进行单点认证。


创建鉴权密钥

要产生认证临时票据,我们需要先在O2OA平台创建一个鉴权密钥配置。

具有管理员权限的用户(具有Manager角色),打开“系统设置”-“安全配置”-“单点登录”,找到“鉴权密钥配置”。



点击“添加鉴权配置”:



此处需要配置一个名称和密钥。
名称:可随意填写,就是我们在调用接口或进行SSO时要传入的client参数。
密钥:可随意填写,用于后续加密,最少8位。

此处我们假设名称填写:oa;密钥填写:platform
填写完成后确定。

然后我们就可以使用此鉴权配置来实现单点登录了。

生成临时票据token

将创建好的鉴权名称和鉴权密钥告知外部系统,由外部系统生成与O2OA单点认证的临时票据。

外部系统必须先生成临时票据(token),这个token是采取DES算法使用密钥对"person#timestamp"文本进行加密获取的。其中:

person:表示指定用户的用户名、唯一编码或员工号。(具体使用哪个要根据外部系统与O2OA的用户关联的字段)

timestamp:表示为1970年1月1日0时0秒到当前时间的毫秒数。(token的有效时间为1分钟)

下面提供一些加密的样例代码:

javascript加密代码样例

我们使用CryptoJS进行DES加密。(GitHub: https://github.com/brix/crypto-js

JavaScript复制代码



function crypDES (value, key) {

    var keyHex = CryptoJS.enc.Utf8.parse(key);

    var xtoken = CryptoJS.DES.encrypt(value, keyHex, {

      mode: CryptoJS.mode.ECB,

      padding: CryptoJS.pad.Pkcs7

    });

    var str = xtoken.ciphertext.toString(CryptoJS.enc.Base64);

    str = str.replace(/=/g, "");

    str = str.replace(/+/g, "-");

    str = str.replace(///g, "_");

    return str;

}



样例:

var login_uid = "test"; 
var time = new Date().getTime(); 
var sso_key = "12345678";
var xtoken = crypDES( login_uid + "#" + time, sso_key );

IOS加密代码样例

/// o2oa DES加密 @param publicKey 加密公钥
func o2DESEncode(code: String, publicKey: String) -> String? {
    if let encode = desEncrypt(code: code, key: publicKey, iv: "12345678", options: (kCCOptionECBMode + kCCOptionPKCS7Padding)) {
        let first = encode.replacingOccurrences(of: "+", with: "-")
        let second = first.replacingOccurrences(of: "/", with: "_")
        let token = second.replacingOccurrences(of: "=", with: "")
        return token
    }else {
        print("加密错误")
        return nil
    }
}
/// DES 加密
func desEncrypt(code: String, key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
    if let keyData = key.data(using: String.Encoding.utf8),
        let data = code.data(using: String.Encoding.utf8),
        let cryptData    = NSMutableData(length: Int((data.count)) + kCCBlockSizeDES) {

        let keyLength              = size_t(kCCKeySizeDES)
        let operation: CCOperation = UInt32(kCCEncrypt)
        let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmDES)
        let options:   CCOptions   = UInt32(options)

        var numBytesEncrypted :size_t = 0

        let cryptStatus = CCCrypt(operation,
                                  algoritm,
                                  options,
                                  (keyData as NSData).bytes, keyLength,
                                  iv,
                                  (data as NSData).bytes, data.count,
                                  cryptData.mutableBytes, cryptData.length,
                                  &numBytesEncrypted)

        if UInt32(cryptStatus) == UInt32(kCCSuccess) {
            cryptData.length = Int(numBytesEncrypted)
            let base64cryptString = cryptData.base64EncodedString()
            return base64cryptString
        }
        else {
            return nil
        }
    }
    return nil
}

样例:

let uid = "test"
let timeInterval = Date().timeIntervalSince1970
let time = CLongLong(round(timeInterval*1000))
let code = "(uid)#(time)"
let xtoken = code.o2DESEncode() ?? ""

Android加密代码样例

fun o2DESEncode(code: String, publicKey: String): String {
    val sutil = CryptDES.getInstance(publicKey)
    var encode = ""
    try {
        encode = sutil.encryptBase64(code)
        Log.d(LOG_TAG,"加密后code:$encode")
        encode = encode.replace("+", "-")
        encode = encode.replace("/", "_")
        encode = encode.replace("=", "")
        Log.d(LOG_TAG,"替换特殊字符后的code:$encode")
    }catch (e: Exception) {
        Log.e(LOG_TAG,"加密失败", e)
    }
    return encode
}

public class CryptDES {
    private Cipher encryptCipher = null;
    private Cipher decryptCipher = null;
    private static CryptDES des = null;
    public static CryptDES getInstance(String des_key) {
        try {
            DESKeySpec key = new DESKeySpec(des_key.getBytes());
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            des = new CryptDES(keyFactory.generateSecret(key));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return des;
    }
    private CryptDES(SecretKey key) throws Exception {
        encryptCipher = Cipher.getInstance("DES");
        decryptCipher = Cipher.getInstance("DES");
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);
        decryptCipher.init(Cipher.DECRYPT_MODE, key);
    }    
    public String encryptBase64 (String unencryptedString) throws Exception {
        // Encode the string into bytes using utf-8
        byte[] unencryptedByteArray = unencryptedString.getBytes("UTF8");
        // Encrypt
        byte[] encryptedBytes = encryptCipher.doFinal(unencryptedByteArray);
        // Encode bytes to base64 to get a string
        byte [] encodedBytes = Base64.encode(encryptedBytes, Base64.DEFAULT);
        return new String(encodedBytes);
    }
    public String decryptBase64 (String encryptedString) throws Exception {
        // Encode bytes to base64 to get a string
        byte [] decodedBytes = Base64.encode(encryptedString.getBytes(), Base64.DEFAULT);
        // Decrypt
        byte[] unencryptedByteArray = decryptCipher.doFinal(decodedBytes);
        // Decode using utf-8
        return new String(unencryptedByteArray, "UTF8");
    }  
}

Java加密代码样例

Crypto.java
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
/**
* encrypt and decrypt utils
* @author O2OA
*
*/
public class Crypto {
    private static final String utf8 = "UTF-8";
    private final static String DES = "DES";
    private final static String cipher_init = "DES";
    public static String encrypt(String data, String key) throws Exception {
        byte[] bt = encrypt(data.getBytes(), key.getBytes());
        String str = Base64.encodeBase64URLSafeString(bt);
        return URLEncoder.encode( str, utf8 );
    }
    public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        SecureRandom sr = new SecureRandom();
        // 从原始密钥数据创建DESKeySpec对象
        DESKeySpec dks = new DESKeySpec(key);
        // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
        SecretKey securekey = keyFactory.generateSecret(dks);
        // Cipher对象实际完成加密操作
        Cipher cipher = Cipher.getInstance(cipher_init);
        // 用密钥初始化Cipher对象
        cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
        return cipher.doFinal(data);
    }
    public static String decrypt(String data, String key) throws IOException, Exception {
        if (StringUtils.isEmpty(data)) {
            return null;
        }
        String str = URLDecoder.decode(data, utf8);
        byte[] buf = Base64.decodeBase64(str);
        byte[] bt = decrypt(buf, key.getBytes());
        return new String(bt);
    }
    public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        SecureRandom sr = new SecureRandom();
        // 从原始密钥数据创建DESKeySpec对象
        DESKeySpec dks = new DESKeySpec(key);
        // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
        SecretKey securekey = keyFactory.generateSecret(dks);
        // Cipher对象实际完成解密操作
        Cipher cipher = Cipher.getInstance(cipher_init);
        // 用密钥初始化Cipher对象
        cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
        return cipher.doFinal(data);
    }
}

调用:

String login_uid = "test"; 
long time = new Date().getTime(); 
String sso_key = "12345678";
String xtoken = Crypto.encrypt( login_uid + "#" + time, sso_key );

实现单点登录

生成token后,外部系统可以直接通过访问以下地址,实现与O2OA的单点认证:

http://servername/x_desktop/sso.html?client={client}&xtoken={token}&redirect={redirect}

其中的client表示使用的鉴权名称;

token表示产生的临时票据token;

redirect表示认证成功后要跳转到的地址;

当然也可以通过访问下面的接口服务,来获取O2OA平台的登录session:

getLogin: x_organization_assemble_authentication 下的SsoAction中的getLogin方法

postLogin: x_organization_assemble_authentication 下的SsoAction中的postLogin方法



上一篇:使用鉴权配置控制接口的调用权限

下一篇:o2server启用数据库配置分库实现分库存储和数据库中间件+分库配置存储