LTPA Cookie原理
- - Web前端 - ITeye博客Lightweight Third-Party Authentication (LTPA)是IBM Websphere和Domino产品中使用单点登录技术. 当服务器配置好LTPA认证方式,用户通过浏览器成功登录后,服务器会自动发送一个session cookie给浏览器;此cookie中包含一个LTPA Token.
1. 什么是LTPA?
Lightweight Third-Party Authentication (LTPA)是IBM Websphere和Domino产品中使用单点登录技术。当服务器配置好LTPA认证方式,用户通过浏览器成功登录后,服务器会自动发送一个session cookie给浏览器;此cookie中包含一个LTPA Token。
2. WebSphere部分
本部分描述适用于已实施WebSphere系列产品应用和Domino平台应用,或WebSphere与Domino之间已完成单点登录。在这样的环境中与构异系统实现单点登录。
u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology%1301558320666%Cy2CAeru5kEElGj0hrvYsKW2ZVsvvcu6Un573aeX55OO4G3EMYWc0e/ZbqDp1z7MS+dLzniuUH4sYWCMpnKdm7ZGabwmV+WcraBl+y+yzwcl722gHVMOnDZAW7U3jEay9Tk2yG4yXkMWU+617xndpVxke2jtS5wIyVVM3q7UDPw=
从WebSphere系统中导出ltpa的key文件,使用文本文件打开,如:
com.ibm.websphere.CreationDate=Thu Mar 31 11\:08\:09 GMT+08\:00 2011 com.ibm.websphere.ltpa.version=1.0 com.ibm.websphere.ltpa.3DESKey=7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g\= com.ibm.websphere.CreationHost=wasserver com.ibm.websphere.ltpa.PrivateKey=N3bnOE1IbiXNsHXxxemC98iiCnmtw3JUuQvdFjEyh9r2gu+FlQRmG8xp5RBltqc6raI4EgYFhTr+t5/tmRQrFqfNKgvujeJZODeCspohi1V4C0qit7DOoqD9xOOn9Rzdb4PIuJM3ekwuBiZZYTYu7q0TANDygc7VbmwoD3xMPCk5svyvFJ/VshPyg5f7Q+VNM8dlIitU4gK9Qp8VZEqjGoXsYYzYYTQgnwAVtR2GfZtXKlf24EPXSkgUz9j8FwTvcylcKwjS22d6eVjciyAzInnxPqxE2iMRPEFDatHZFox3flsqBswmeDQrAGv8zIiffgP1DLKdjozUyAG+50v97xx7u1RtIrB4B01ik8DuLhw\= com.ibm.websphere.ltpa.Realm=VGOLiveRealm com.ibm.websphere.ltpa.PublicKey=AM04If2+ElGSyVRF0ZEesgvC59vGw8gSIfptjfoXj8iz4C7Ip/KVAu2PDkpQi3LUN/FgVF696tmsegBThks9rmMMHzOix/vGP2721dQZKbD7plOLdWtiY2AYZChsBVkOF26DfiWJ6euxD+a+KNcrfDnu2AXRC/tKncIUJV4LbeJdAQAB
以下代码为解析从WebSphere或Domino发送过来的LTPAToken Cookie以Java为例:
01 …
02 // LTPA 3DES 密钥
03 String ltpa3DESKey = "7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g=" ;
04 // LTPA 密钥密码
05 String ltpaPassword = "Passw0rd" ;
06 try {
07 // 获得加密key
08 byte [] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword);
09 // 使用加密key解密ltpa Cookie
10 String ltpaPlaintext = new String(decryptLtpaToken(tokenCipher,
11 secretKey));
12 displayTokenData(ltpaPlaintext);
13 } catch (Exception e) {
14 System.out.println( "Caught inner: " + e);
15 }
16 …
17 //获得安全Key
18 private static byte [] getSecretKey(String ltpa3DESKey, String password)
19 throws Exception {
20 // 使用SHA获得key密码的hash值
21 MessageDigest md = MessageDigest.getInstance( "SHA" );
22 md.update(password.getBytes());
23 byte [] hash3DES = new byte [ 24 ];
24 System.arraycopy(md.digest(), 0 , hash3DES, 0 , 20 );
25 // 使用0替换后4个字节
26 Arrays.fill(hash3DES, 20 , 24 , ( byte ) 0 );
27 // BASE64解码 ltpa3DESKey
28 byte [] decode3DES = Base64.decodeBase64(ltpa3DESKey.getBytes());
29 // 使用key密码hash值解密已Base64解码的ltpa3DESKey
30 return decrypt(decode3DES, hash3DES);
31 }
32 //解密LtpaToken
33 public static byte [] decryptLtpaToken(String encryptedLtpaToken, byte [] key)
34 throws Exception {
35 // Base64解码LTPAToken
36 final byte [] ltpaByteArray = Base64.decodeBase64(encryptedLtpaToken
37 .getBytes());
38 // 使用key解密已Base64解码的LTPAToken
39 return decrypt(ltpaByteArray, key);
40 }
41 // DESede/ECB/PKC5Padding解方法
42 public static byte [] decrypt( byte [] ciphertext, byte [] key)
43 throws Exception {
44 final Cipher cipher = Cipher.getInstance( "DESede/ECB/PKCS5Padding" );
45 final KeySpec keySpec = new DESedeKeySpec(key);
46 final Key secretKey = SecretKeyFactory.getInstance( "TripleDES" )
47 .generateSecret(keySpec);
48 cipher.init(Cipher.DECRYPT_MODE, secretKey);
49 return cipher.doFinal(ciphertext);
50 }
51 …
解析出来的LTPAToken信息以%分隔
Websphere LTPA生成时的签名信息是由用户DN和一些用户其他信息组成字符串,使用私有密钥进行签名,由于不清楚这些信息的组成,故无法产生正确的LTPA。
本部分的描述仅适用于单一的Domino平台应用与构异系统实现单点登录。
Base64解码/编码所需Jar包: apache-commons-codec-1.3.jar以上
01 import org.apache.commons.codec.binary.Base64;
02 …...
03 final String CHARSET = "Cp850" ;
04 byte [] dominoSecret = Base64.decodeBase64(ltpaDominoSecret.getBytes());
05 byte [] ltpa = Base64.decodeBase64(ltpaToken.getBytes());
06 ByteArrayInputStream stream = new ByteArrayInputStream(ltpa);
07 int usernameLength = ltpa.length – 40 ;
08 byte header[] = new byte [ 4 ];
09 byte creation[] = new byte [ 8 ];
10 byte expires[] = new byte [ 8 ];
11 byte username[] = new byte [usernameLength];
12 byte [] sha = new byte [ 20 ];
13 // 读取LTPAToken版本号
14 stream.read(header, 0 , 4 );
15 if (header[ 0 ] != 0 || header[ 1 ] != 1 || header[ 2 ] != 2 || header[ 3 ] != 3 )
16 throw new IllegalArgumentException( "Invalid ltpaToken format" );
17 // 读取开始时间
18 stream.read(creation, 0 , 8 );
19 // 读取到期时间
20 stream.read(expires, 0 , 8 );
21 // 读取Domino用户DN
22 stream.read(username, 0 , usernameLength);
23 // 读取SHA校验和
24 stream.read(sha, 0 , 20 );
25 // 转换用户名
26 char characters[] = new char [usernameLength];
27 try {
28 InputStreamReader isr = new InputStreamReader(
29 new ByteArrayInputStream(username),
30 CHARSET);
31 isr.read(characters);
32 } catch (Exception e) {
33 }
34 // 获得Domino用户DN
35 String dn = new String(characters);
36 // 获得创建时间
37 Date creationDate = new Date(
38 Long.parseLong( new String(creation), 16 ) * 1000 );
39 // 获得到期时间
40 Date expiresDate = new Date(
41 Long.parseLong( new String(expires), 16 ) * 1000 );
42 …...
43 // 创建LTPA Token
44 ByteArrayOutputStream ostream = new ByteArrayOutputStream();
45 try {
46 // LTPA Token版本号
47 ostream.write(header);
48 // 创建时间
49 ostream.write(creation);
50 // 过期时间
51 ostream.write(expires);
52 // Domino用户DN,如CN=SquallZhong/O=DigiWin
53 ostream.write(username);
54 // Domino LTPA 密钥
55 ostream.write(dominoSecret);
56 ostream.close();
57 } catch (IOException e) {
58 throw new RuntimeException(e);
59 }
60 // 进行 SHA-1 校验和
61 MessageDigest md;
62 try {
63 md = MessageDigest.getInstance( "SHA-1" );
64 md.reset();
65 } catch (NoSuchAlgorithmException e) {
66 throw new RuntimeException(e);
67 }
68 byte [] digest = md.digest(ostream.toByteArray());
69 // 完成 SHA-1 校验和,digest长度为20
70 boolean valid = MessageDigest.isEqual(digest, sha);
01 /**
02 * 为指定用户创建有效的LTPA Token.创建时间为<tt>now</tt>.
03 *
04 * @param username
05 * - 用户名,注:使用用户全称,如:CN=SquallZhong/O=VGOLive Technology
06 * @param creationTime
07 * - 创建时间
08 * @param durationMinutes
09 * - 到期时间,单位:分钟
10 @param ltpaSecretStr
11 * - Domino Ltpa 加密字符串
12 * @return - 返回已Base64编码的Ltpa Cookie.
13 * @throws NoSuchAlgorithmException
14 * @throws Base64DecodeException
15 */
16 public static String createLtpaToken(String username,
17 GregorianCalendar creationTime, int durationMinutes,
18 String ltpaSecretStr) throws NoSuchAlgorithmException {
19 // Base64解码ltpaSecretStr
20 byte [] ltpaSecret = Base64.decodeBase64(ltpaSecretStr.getBytes());
21 // 用户名字节数组
22 byte [] usernameArray = username.getBytes();
23 byte [] workingBuffer = new byte [preUserDataLength
24 + usernameArray.length + ltpaSecret.length];
25
26 // 设置ltpaToken版本至workingBuffer
27 System.arraycopy(ltpaTokenVersion, 0 , workingBuffer, 0 ,
28 ltpaTokenVersion.length);
29 // 获得过期时间,过期时间=当前时间+到期时间(分钟)
30 GregorianCalendar expirationDate = (GregorianCalendar) creationTime
31 .clone();
32 expirationDate.add(Calendar.MINUTE, durationMinutes);
33
34 // 转换创建时间至16进制字符串
35 String hex = dateStringFiller
36 + Integer.toHexString(
37 ( int ) (creationTime.getTimeInMillis() / 1000 ))
38 .toUpperCase();
39 // 设置创建时间至workingBuffer
40 System.arraycopy(hex.getBytes(), hex.getBytes().length
41 - dateStringLength, workingBuffer, creationDatePosition,
42 dateStringLength);
43
44 // 转换过期时间至16进制字符串
45 hex = dateStringFiller
46 + Integer.toHexString(
47 ( int ) (expirationDate.getTimeInMillis() / 1000 ))
48 .toUpperCase();
49 // 设置过期时间至workingBuffer
50 System.arraycopy(hex.getBytes(), hex.getBytes().length
51 - dateStringLength, workingBuffer, expirationDatePosition,
52 dateStringLength);
53
54 // 设置用户全称至workingBuffer
55 System.arraycopy(usernameArray, 0 , workingBuffer, preUserDataLength,
56 usernameArray.length);
57
58 // 设置已Base64解码ltpaSecret至workingBuffer
59 System.arraycopy(ltpaSecret, 0 , workingBuffer, preUserDataLength
60 + usernameArray.length, ltpaSecret.length);
61 // 创建Hash字符串
62 byte [] hash = createHash(workingBuffer);
63
64 // ltpaToken版本+开始时间(16进制)+到期时间(16进制)+用户全名+SHA-1(ltpaToken版本+开始时间(16进制)+到期时间(16进制)+用户全名)
65 byte [] outputBuffer = new byte [preUserDataLength + usernameArray.length
66 + hashLength];
67 System.arraycopy(workingBuffer, 0 , outputBuffer, 0 , preUserDataLength
68 + usernameArray.length);
69 System.arraycopy(hash, 0 , outputBuffer, preUserDataLength
70 + usernameArray.length, hashLength);
71 // 返回已Base64编码的outputBuffer
72 return new String(Base64.encodeBase64(outputBuffer));
73 }
74 …...
F5 iRule代码如下:
when RULE_INIT {
01 set cookie_name "LtpaToken" # 不更改
02 set ltpa_version "\x00\x01\x02\x03" # 不更改
03 set ltpa_secret "b64encodedsecretkey" # 从Domino SSO文档获得ltpa密钥
04 set ltpa_timeout "1800" # 从Domino SSO文档中获得过期时间,单位:秒
05 }
06
07 when HTTP_REQUEST {
08 #
09 # Do your usual F5 HTTP authentication here
10 #
11 # Initial values
12 set creation_time_temp [clock seconds]
13 set creation_time [ format % X $creation_time_temp]
14 set expr_time_temp [expr { $creation_time_temp + $::ltpa_timeout}]
15 set expr_time [ format % X $expr_time_temp]
16 set username [HTTP::username]
17 set ltpa_secret_decode [b64decode $::ltpa_secret]
18 # First part of token
19 set cookie_data_raw {}
20 append cookie_data_raw $::ltpa_version
21 append cookie_data_raw $creation_time
22 append cookie_data_raw $expr_time
23 append cookie_data_raw $username
24 append cookie_data_raw $ltpa_secret_decode
25 # SHA1 of first part of token
26 set sha_cookie_raw [sha1 $cookie_data_raw]
27 # Final not yet encoded token
28 set ltpa_token_raw {}
29 append ltpa_token_raw $::ltpa_version
30 append ltpa_token_raw $creation_time
31 append ltpa_token_raw $expr_time
32 append ltpa_token_raw $username
33 append ltpa_token_raw $sha_cookie_raw
34 # Final Base64 encoded token
35 set ltpa_token_final [b64encode $ltpa_token_raw]
36 # Insert the cookie
37 HTTP::cookie insert name $::cookie_name value $ltpa_token_final
38 }
39 # Remove Authorization HTTP header to avoid using basic authentication
40 if { [HTTP::header exists "Authorization" ] } {
41 HTTP::header remove "Authorization"
42 }
43 }
来源连接 http://www.cnblogs.com/hannover/archive/2011/05/29/2061798.html