首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 https://juejin.cn/post/6941642435189538824
Github : 👉 https://github.com/black-ant
CASE 备份 : 👉 https://gitee.com/antblack/case
- 纯约束型协议 : OAuth , SAML , OIDC , CAS ,LTPA
- 服务器类协议 : RADIUS , Kerberos , ADFS
- 认证方式类 : OTP , 生物认证 (人脸 , 声纹 , 指纹)
- 认证服务器(附带) : AD , LDAP , ADFS
这一篇聊一聊 LTPA 这个协议 , 这个算是一个很少见的协议 , 专属于 IBM , 我们只是简单的说说它…
一 . 前言
ltpa 全称 Lightweight Third-Party Authentication , 即轻量级第三方认证 . 这个协议看起来很复杂 , 其实使用的时候会感觉很简单 , 用法和 JWT 很类似.
LTPA 是 一项 IBM 协议 ,用于 在 WebSphere®Application Server 中提供基于 cookie 或二进制安全性令牌的认证机制 ,其支持 单点登录 SSO .
整个流程中包括多个服务器 , 例如WebSphere® 和 DataPower®。要对其中一个或多个服务器实现单点登录解决方案,您可以将 WebSEAL 配置为支持 LTPA 认证 .
目的 : LTPA 令牌认证的目的是将 LTPA 令牌从第一个 Web Service(其认证生成客户机)流动到下游 Web Service , 简单点说就是 IDP Server 生成令牌 , 下发到下游服务 .
Cookie 作用 : 具有有效 LTPA cookie 的用户可以访问与第一个服务器属于同一身份验证域的服务器,并将自动进行身份验证。
Cookie 本身包含有关已经验证的用户、用户要验证的领域(例如 LDAP 服务器)和时间戳的信息。所有这些信息之二用共享的3DES 密钥加密,并由公/私密密钥对签名。这一切都很好,直到您试图执行一些故障排除,并意识到无法查看这些 cookie 的内部。
二 . 深入知识点
2.1 流程分析
宏观流程: User —> ISAM SSO (WebSeal) - LTPA goes here ----> backend server WAS
宏观解释 :
- 后端WAS服务器本身不做任何身份验证。
- 用户对SSO服务器进行身份验证,SSO服务器将加密的LTPA令牌发送到后端服务器,该服务器包含用户名(通常只有用 户名、组成员,但从不包含密码)。
- 后端服务器解密LTPA并将其作为LTPA并信任它。
- 然后后端应用服务器(WAS)将此身份验证详细信息传递给应用程序。
流程详情:
- 当未经认证的用户对 WebSEAL 受保护资源发出请求时,WebSEAL 将首先确定是否提供了 LTPA Cookie。
- 如果提供了 LTPA Cookie,那么它将验证此 Cookie 的内容,并在验证成功后根据此 Cookie 中包含的用户名和到期时间创建新会话。
- 如果未提供 LTPA Cookie,那么 WebSEAL 将继续使用其他已配置的认证机制对用户进行认证。
- 完成认证操作后,将在 HTTP 响应中插入新的 LTPA Cookie,并将其传递回客户机以供其他支持 LTPA 的认证服务器使用。
2.2 LTPA 架构
LTPA 协议是基于 WebSphere 实现的 !
WebSphere 是什么 :
WebSphere 是一个IBM 产品 , 它支持在一个因特网域中的一组web 服务器间使用单一登录的认证策略 ,通过密码术 可支持分布式环境的安全性 ,web 用户只需对 WebSphere Application Server 或 Domino 服务器认证一次 ,认证将会通过服务器进行共享.
**PS: 和联合认证有相同的思想 … **
一个通过有效的LTPA Cookie能够在同一个认证域中所有服务器被自动认证。此Cookie中包含认证信息和时间戳。这些信息通过共享的3DES Key进行了bis 加密。使用公共密钥/私有密钥进行签名。
2.3 LTPA Token 令牌
2.3.1 令牌的属性
LTPA 令牌包含如下属性 :
- title : 必须 ,策略的标题 ,字符串
- description : 非必须 , 对策略的描述 ,字符串
- key : LTPA 秘钥 ,必须 ,用于生成 LTPA 令牌 的LTPA 秘钥名称 - 包括 :
- my-ltpa-key (策略默认为 1.0),my-ltpa-key:2.0.0 (使用特定版本),my-ltpa-key:latest ( 最新版本 )
- authenticatedUserName : 已认证用户名
- tokenVersion : 令牌版本
- tokenOutput : 令牌用于放置已生成令牌的位置 (cookie 头 ,WSSec 头 )
- tokenExpiry : 整数 ,令牌过期时间
2.3.2 令牌的案例
首先,这个 cookie 由以下部分组成,以%进行分隔:
- 用户信息,格式为u:user\:<RealmName>/<UserDN>,
如:u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology
- 过期时间
- 签名信息,如:
u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology%1301558320666%Cy2CAeru5kEElGj0hrvYsKW2ZVsvvcu6Un573aeX55OO4G3EMYWc0e/ZbqDp1z7MS+dLzniuUH4sYWCMpnKdm7ZGabwmV+WcraBl+y+yzwcl722gHVMOnDZAW7U3jEay9Tk2yG4yXkMWU+617xndpVxke2jtS5wIyVVM3q7UDPw=
2.3.3 令牌的区别
LTPA (Version 1): http://www.ibm.com/websphere/appserver/tokentype/5.0.2/LTPA
LTPA2: http://www.ibm.com/websphere/appserver/tokentype/LTPAv2
LtpaToken
LtpaToken 用于与 WebSphere Application Server 的前发行版进行互操作。此令牌仅包含认证身份属性。
LtpaToken 针对 WebSphere Application Server V5.1.0.2 之前的发行版(对于 z/OS®)或 V5.1.1(对于分布式系统)生成。
LtpaToken2
LtpaToken2 包含更强的加密功能,并且您能够向令牌添加多个属性。此令牌包含认证身份和其他信息(例如,属性)。属性用于联系原始登录服务器和唯一高速缓存密钥。如果在确定唯一性时要考虑除身份以外的其他内容,还将使用属性来查找主题。
注意 :
为了允许运行不同版本WebSphere Application Server的服务器之间的互操作性,默认情况下,在将绑定配置为期望LTPA2令牌时,Version 7.0及更高版本的JAX-WS web服务安全运行时可以成功地使用LTPA Version 1令牌。但是,您可以将JAX-WS运行时的绑定配置为只接受LTPA2令牌。有关更多信息,请参阅有关身份验证生成器或使用者令牌设置的文档。
2.4 LTPA Cookie
Cookie 加密方式
LTPA cookie 在 DESede/ECB/PKCS5Padding 模式下使用3DES 密钥进行加密。真正的密钥也是在 DESede/ECB/PKCS5Padding 模式下使用3DES 加密的,其中使用用0X0最多24字节填充的所提供密码的 SHA 散列。要解密实际令牌,可以获取密码,生成一个3DES 密钥,解密加密密钥,然后解密 cookie 数据。还有一个公钥/私钥对用于对 cookie 进行签名。
- 使用 3DES 秘钥 进行 DESede/ECB/PKCS5P 加密
- 秘钥采用 DESede/ECB/PKCS5P进行加密
- 通过提供的密码进行 SHA Hash
- 对生成的24字节秘钥 进行 Base64 编码
Cookie 的元素 @ https://my.oschina.net/psuyun/blog/298429
- LTPA token 版本(4字节)
- 创建时间(8字节)
- 过期时间(8字节)
- 用户名(可变长度)
- Domino LTPA 密钥(20字节)
在与 Domino 做 SSO 的时候,会使用 LTPA Token的认证方式,本文描述它的生成原理,通过它我们可以自己编码生成身份认证的 cookie,实现 SSO。
首先,这个 cookie 由以下部分组成
- LTPA token 版本(4字节)
- 创建时间(8字节)
- 过期时间(8字节)
- 用户名(可变长度)
- Domino LTPA 密钥(20字节)
接下来分别说明各部分的具体内容:
- LTPA token 版本目前 Domino 只有一种值:0x0001
- 创建时间为以十六进制方式表示的Unix time,例如:2009-04-09 13:52:42 (GMT +8) = 1239256362 = 49DD8D2A。
- 过期时间=创建时间 + SSO 配置文档的过期时间(LTPA_TokenExpiration域)
- 用户名为 Names 中用户文档的FullName域值;如:Squall Zhong/Digiwin
- Domino LTPA 密钥通过 Base64编码后,保存在 SSO 配置文档的LTPA_DominoSecret域中
当然不能将密钥直接发送给浏览器,所以将上述部分合并起来(如上图),计算 SHA-1 校验和
然后用 SHA-1 校验和替换掉 Domino LTPA 密钥,最后再将内容通过 Base64 编码,形成最终的 cookie 发送给浏览器。这样如果 cookie 中的任何内容被修改,校验和就不对了,达到了防篡改的效果。所以最终LTPA Cookie所得到的值为以下公式组成:
- SHA-1=LTPA版本号+创建时间+过期时间+用户名+Domino LTPA 密钥
- LTPA Cookie= Base64(LTPA版本号+创建时间+过期时间+用户名+SHA-1)
三 . 实践
3.1 一个 LTPA 的格式
[token] = BASE64([header][creation time][expiration time][username][SHA-1 hash])
Header: LtpaToken 版本(长度4),Domino的固定为[0x00][0x01][0x02][0x03]
Creation time: 创建时间戳(长度8),格式为Unix time比如[2010-03-12 00:21:49]为4B99189D
expiration time:过期时间戳(长度8) 同上
username: 用户名(长度不定)
SHA-1 hash:SHA-1校验和(长度20)
- 由前面所说的密钥和其余的Token资料合并而成,合成公式如下
- [SHA-1 hash] = SHA-1([header][creation time][expiration time][username][shared secret])
3.2 Domain 解析 Token
- Base64解码LtpaToken。
- 截取最前面20字节,最后面20字节,中间部分就是用户名。如果用户名在本系统中不正确,返回无效的LtpaToken。
- 截取最后面20字节,是SHA-1校验和。用Token中的其余部分和本系统中的密钥生成新的SHA-1校验和,如果2个校验和不匹配。返回无效的LtpaToken。
- 当前服务器时间必须大于创建时间,小于失效时间。否则返回无效的LtpaToken。
- 最后解析通过了,完成用户的登录
3.3 Java 实现 LTPA Cookie 解析
转载自 @ https://www.cnblogs.com/cmt/p/14580194.html?from=https%3A%2F%2Fwww.cnblogs.com%2Fhannover%2Farchive%2F2011%2F05%2F29%2F2061798.html
解析方法
// LTPA 3DES 密钥
String ltpa3DESKey = "7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g=";
// LTPA 密钥密码
String ltpaPassword = "Passw0rd";
try {
// 获得加密key
byte[] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword);
// 使用加密key解密ltpa Cookie
String ltpaPlaintext = new String(decryptLtpaToken(tokenCipher,
secretKey));
displayTokenData(ltpaPlaintext);
} catch (Exception e) {
System.out.println("Caught inner: " + e);
}
//获得安全Key
private static byte[] getSecretKey(String ltpa3DESKey, String password)
throws Exception {
// 使用SHA获得key密码的hash值
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(password.getBytes());
byte[] hash3DES = new byte[24];
System.arraycopy(md.digest(), 0, hash3DES, 0, 20);
// 使用0替换后4个字节
Arrays.fill(hash3DES, 20, 24, (byte) 0);
// BASE64解码 ltpa3DESKey
byte[] decode3DES = Base64.decodeBase64(ltpa3DESKey.getBytes());
// 使用key密码hash值解密已Base64解码的ltpa3DESKey
return decrypt(decode3DES, hash3DES);
}
//解密LtpaToken
public static byte[] decryptLtpaToken(String encryptedLtpaToken, byte[] key)
throws Exception {
// Base64解码LTPAToken
final byte[] ltpaByteArray = Base64.decodeBase64(encryptedLtpaToken
.getBytes());
// 使用key解密已Base64解码的LTPAToken
return decrypt(ltpaByteArray, key);
}
// DESede/ECB/PKC5Padding解方法
public static byte[] decrypt(byte[] ciphertext, byte[] key)
throws Exception {
final Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
final KeySpec keySpec = new DESedeKeySpec(key);
final Key secretKey = SecretKeyFactory.getInstance("TripleDES")
.generateSecret(keySpec);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(ciphertext);
}
生成 LTPA Token
/**
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 BIG-IP创建Domino LTPAToken
when RULE_INIT {
set cookie_name "LtpaToken" # Don't change this
set ltpa_version "\x00\x01\x02\x03" # Don't change this
set ltpa_secret "b64encodedsecretkey" # Set this to the LTPA secrey key from your Lotus Domino LTPA configuration
set ltpa_timeout "1800" # Set this to the timeout value from your Lotus Domino LTPA configuration
}
when HTTP_REQUEST {
#
# Do your usual F5 HTTP authentication here
#
# Initial values
set creation_time_temp [clock seconds]
set creation_time [format %X $creation_time_temp]
set expr_time_temp [expr { $creation_time_temp + $::ltpa_timeout}]
set expr_time [format %X $expr_time_temp]
set username [HTTP::username]
set ltpa_secret_decode [b64decode $::ltpa_secret]
# First part of token
set cookie_data_raw {}
append cookie_data_raw $::ltpa_version
append cookie_data_raw $creation_time
append cookie_data_raw $expr_time
append cookie_data_raw $username
append cookie_data_raw $ltpa_secret_decode
# SHA1 of first part of token
set sha_cookie_raw [sha1 $cookie_data_raw]
# Final not yet encoded token
set ltpa_token_raw {}
append ltpa_token_raw $::ltpa_version
append ltpa_token_raw $creation_time
append ltpa_token_raw $expr_time
append ltpa_token_raw $username
append ltpa_token_raw $sha_cookie_raw
# Final Base64 encoded token
set ltpa_token_final [b64encode $ltpa_token_raw]
# Insert the cookie
HTTP::cookie insert name $::cookie_name value $ltpa_token_final
}
# Remove Authorization HTTP header to avoid using basic authentication
if { [HTTP::header exists "Authorization"] } {
HTTP::header remove "Authorization"
}
}
总结
LTPA 是一个企业痕迹很重的协议 ,它基本上归属于 IBM , 这就意味着使用如果有困难 , 文档不一定能解决 , 而请求服务支持可不是一个简单的事情 .
当然 ,在使用中 , ltpa cookie 也可以被当成一种 JWT Token 的一种生成方式 , 将其放在Cookie 中 ,再基于 SSO 认证 , 这不算一个 LTPA 体系 , 仅仅是使用了 LTPA 的 Cookie 生成方式 .