SpringSecurityOAuth2中关于TokenStore实现类JwtTokenS。。。
1、前⾔
在中,我们分析了在OAuth2中,Token是如何创建的,同时也了解了TokenStore是如何管理Token的,并详细分析了InMemoryTokenStore 实现类的逻辑,⽽JdbcTokenStore 和 RedisTokenStore 实现思路是类似的,但是JwtTokenStore 的实现类就和InMemoryTokenStore不⼀样了,这篇内容就详细分析JwtTokenStore是如何实现Token的管理的。⾸先,了解⼀下JWT中的⼀些概念,如下所⽰:
2、概念
网络签名2.1、JWK (JSON Web Key)、JWKS
JSON Web Key (JWK)是RFC规范定义的⼀种数据结构,⽤来表⽰密码密钥的结构。详情可以参考:。JWKS 是 JWK的数组。
其中,JWK的参数定义如下:
1. “kty”(key type)
表⽰密钥使⽤的加密算法,⽐如“RSA”或者“EC”等,是⼤⼩写敏感的字符串。JWK中必须携带这个字段。
2. “u”(Public Key U)
表⽰公钥的使⽤⽬的。指⽰公钥是⽤于加密数据还是⽤于验证数据上的签名。可以是如下值:
1>、“sig”(signature)
2>、“enc”(encryption)
⼤⼩写敏感。可以选择性携带。
3. “key_ops”(Key Operations)
标识要使⽤密钥的操作。⽤于可能存在公共、私有或对称密钥的⽤例。key_ops字段的值是数组,数组可以包含以下值:
1>、“sign”(计算数字签名或MAC)
2>、“verify”(验证数字签名或MAC)
3>、“encrypt”(加密内容)
4>、“decrypt”(解密内容以及验证解密)
5>、“warpKey”(加密密钥)
6>、“unwrapKey”(解密密钥并验证解密)
7>、“deriveKey”(产⽣密钥)
8>、“deriveBits”(产⽣bits,但是该bits不⽤于密钥)
4. “alg”(algorithm)
卷心菜的做法大全标识⽤于密钥的算法。
5. “kid”(Key ID)
⽤于匹配密钥。主要在JWK集合中选择jwk。
6. “n”(公钥的模值)
7. “e”(公钥的指数)
8. “x5u”(X.509 URL)
9. “x5c”(X.509 Certificate Chain)
0. “x5t”(X.509 Certificate SHA-1 Thumbprint)
黎塞留级战列舰1. “x5t#S256”(X.509 Certificate SHA-256 Thumbprint)
上述参数介绍内容来⾃。
2.2、JWS(JSON Web Signature)、JWT(Json Web Token)、JWE(JSON Web Encryption)
JWT、JWS、JWE三者间的关系
JWS、JWE是JWT的⼀种实现,⽽⽹上⼤多数介绍JWT的⽂章实际介绍的都是JWS(JSON Web Signature),也往往导致了⼈们对于JWT的误解,但是JWT并不等于JWS。
JWS(JSON Web Signature)
JWS是⼀个有着简单的统⼀表达形式的字符串,通过使⽤数字技术保护传输的内容不被修改,可以保证数据的完整性,但是由于仅采⽤Ba64对消息内容编码,因此不保证数据的不可泄露性,所以不适合⽤于传输敏感数据。
JWS字符串包括了头部(Header)、载荷(PayLoad)、签名(signature)三部分。
其中,头部(Header)⽤于描述关于该JWT的最基本的信息,例如其类型以及签名所⽤的算法等,JSON内容要经Ba64 编码⽣成字符串成为Header。
载荷(PayLoad)的五个字段都是由JWT的标准所定义的:
1. iss: 该JWT的签发者
2. sub: 该JWT所⾯向的⽤户
3. aud: 接收该JWT的⼀⽅
4. exp(expires): 什么时候过期,这⾥是⼀个Unix时间戳
5. iat(issued at): 在什么时候签发的
载荷(PayLoad)后⾯的信息可以按需补充。JSON内容要经Ba64 编码⽣成字符串成为PayLoad。
签名(signature)这个部分header与payload通过header中声明的加密⽅式,使⽤密钥cret进⾏加密,⽣成签名,保证数据在传输过程中不被修改。
猪肉炒青菜详情可以参考:、。
JWE(JSON Web Encryption)
相对于JWS,JWE则同时保证了安全性与数据完整性。JWE由五部分组成:
具体⽣成步骤为:
1. JOSE含义与JWS头部相同。
2. ⽣成⼀个随机的Content Encryption Key (CEK)。
3. 使⽤RSAES-OAEP加密算法,⽤公钥加密CEK,⽣成JWE Encrypted Key。
4. ⽣成JWE初始化向量。
5. 使⽤AESGCM加密算法对明⽂部分进⾏加密⽣成密⽂Ciphertext,算法会随之⽣成⼀个128位的认证标记Authentication Tag。
6. 对五个部分分别进⾏ba64编码。
可见,JWE的计算过程相对繁琐,不够轻量级,因此适合与数据传输⽽⾮token认证,但该协议也⾜够安全可靠,⽤简短字符串描述了传输内容,兼顾数据的安全性与完整性。
详情可以参考:、、。
钟兴民
JWT(JSON Web Token)
JWT(json web token)是设计⼀种简洁,安全,⽆状态的token的实现规范rfc7519,通常⽤于⽹络请求⽅和⽹络接收⽅之间的⽹络请求认证。JWS和JWE都是JWT的⼀种实现。
JWT是由三段信息构成的,将这三段信息⽂本⽤.链接⼀起就构成了Jwt字符串。其中,第⼀部分我们
称它为头部(header),第⼆部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature)。当拥有签名的JWT被称为JWS,也就是签了名的JWS;没有签名部分的JWT被称为noncure JWT也就是不安全的JWT,此时header中声明的签名算法为none。
详情可以参考: 、。
总之,⼀句话概况上述的概念就是:JWT是为了在⽹络应⽤环境间传递声明⽽执⾏的⼀种基于JSON的开放标准((RFC 7519);⽽JWK 是⽤来加密JWT的密钥或者密钥对;JWS,也就是JWT Signature,即在JWT基础上,在头部声明签名算法,并在最后添加上签名,保证可以校验token的完整性;JWE,也是JWT的⼀种实现,不仅能够保证数据的完整性,还可以保证数据的安全性,即不会被第三⽅解码获取原始数据。
3、JwtTokenStore
和InMemoryTokenStore相⽐,JwtTokenStore实现类没有持久化Token信息,但是JwtTokenStore实现了access tokens 和authentications的相互转换,该功能通过JwtAccessTokenConverter对象实现,因此当需要authentications信息时,直接通过access tokens就可以获取到。因为不需要存储Token信息,所以TokenStore接⼝中定义的⼀些⽅法,在JwtTokenStore的实现类中就不需要实现,如下所⽰:
3.1、JwtTokenStore实现类中的空⽅法
以下空⽅法的实现,均因为不需要存储Token这种特性造成的。
1. storeAccessToken()⽅法 不需要存储Token信息
2. removeAccessToken()⽅法,因为没有存储,所以也不需要删除,同时也造成了JWT的⽅式不能使得Token过期,这也是JWT⽅式
的⼀个致命缺点。
3. storeRefreshToken()⽅法
4. removeAccessTokenUsingRefreshToken()⽅法
5. getAccessToken()⽅法,调⽤该⽅法会直接返回null
6. findTokensByClientIdAndUrName()⽅法,返回空集合
7. findTokensByClientId()⽅法,返回空集合
3.2、readAuthentication()⽅法、readAuthenticationForRefreshToken()⽅法
根据AccessToken对象查询对应的OAuth2Authentication(认证的⽤户信息),在JwtTokenStore实现类中,是通过jwtTokenEnhancer的extractAuthentication()⽅法,实现了token字符串和OAuth2Authentication对象的转换。jwtTokenEnhancer后续专门分析。具体实现如下:
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token){
return Value());
}
@Override
public OAuth2Authentication readAuthentication(String token){
actAuthentication(jwtTokenEnhancer.decode(token));
}
@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token){
return Value());
}
3.3、readAccessToken()⽅法浪人情歌吉他谱
根据AccessToken的value值查询对应的token对象,该⽅法是通过jwtTokenEnhancer的extractAccessToken()⽅法实现。具体实现如下:
@Override
public OAuth2AccessToken readAccessToken(String tokenValue){
OAuth2AccessToken accessToken =convertAccessToken(tokenValue);
//判断是否是刷新token,当additionalInformation中包括ACCESS_TOKEN_ID(AccessTokenConverter.ATI,“ati”)时,就是刷新token
if(jwtTokenEnhancer.isRefreshToken(accessToken)){
throw new InvalidTokenException("Encoded token is a refresh token");
}
return accessToken;
}
private OAuth2AccessToken convertAccessToken(String tokenValue){
actAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
}
3.4、readRefreshToken()⽅法
根据token字符串查询对应的token对象,实际上还是通过jwtTokenEnhancer的extractAccessToken()先获取
OAuth2AccessToken对象,然后判断该对象是否是刷新对,如果是的话,就创建默认的DefaultOAuth2RefreshToken对象,具体实现如下所⽰:
@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue){
OAuth2AccessToken encodedRefreshToken =convertAccessToken(tokenValue);
OAuth2RefreshToken refreshToken =createRefreshToken(encodedRefreshToken);
if(approvalStore !=null){
OAuth2Authentication authentication =readAuthentication(tokenValue);
UrAuthentication()!=null){
String urId = UrAuthentication().getName();
String clientId = OAuth2Request().getClientId();
Collection<Approval> approvals = Approvals(urId, clientId);
Collection<String> approvedScopes =new HashSet<String>();
for(Approval approval : approvals){
if(approval.isApproved()){
我是中国人英语approvedScopes.Scope());
}
}
if(!OAuth2Request().getScope())){
return null;
}
}
}
return refreshToken;
}
在上述readRefreshToken()⽅法中,⾸先通过convertAccessToken()⽅法把token字符串转换成OAuth2AccessToken对象,实现如下:
private OAuth2AccessToken convertAccessToken(String tokenValue){
actAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
}
然后,在通过createRefreshToken()⽅法,实现OAuth2AccessToken对象转OAuth2RefreshToken对象,实现如下:
private OAuth2RefreshToken createRefreshToken(OAuth2AccessToken encodedRefreshToken){
//⾸先判断,encodedRefreshToken是不是刷新token,即是否带“ati”标识
if(!jwtTokenEnhancer.isRefreshToken(encodedRefreshToken)){
throw new InvalidTokenException("Encoded token is not a refresh token");
}
//判断是否带有expiration参数,然后创建对应的DefaultExpiringOAuth2RefreshToken或DefaultOAuth2RefreshToken对象。
Expiration()!=null){
return new Value(),
}
return new Value());
}
教你做酸辣粉 再然后,当approvalStore对象不为空时,校验当前token是否有权限访问,没有权限的话,直接返回null,具体实现如下: