JWT有两个执行时机,一个是在用户认证成功时,对authentication进行加密,得到token发给用户,一个是当用户携带加密后的token访问服务时,JWT将token进行解码,再把token提取为authentication
这两个时机的执行都是依靠JwtAccessTokenConverter来完成的
JWT组件主要包括:sigingKey(对策密钥)、accessTokenConvert(accessToken转化器)、tokenStore(密钥策略:包含token解析、存储等)、authenticationTokenConvert(authentication转化器,包含再accessTokenConvert中)
注意:上面说的只是组件,JWT暴露给SpringSecurity的服务是tokenService,SpringSecurity也是通过tokenService来完成token的生成、解析以及存储的
@Configuration
public class JWTConfig {
@Value("${siging-key}")
private String SIGNING_KEY;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来解密
ClientDefaultAccessTokenConverter accessTokenConverter = new ClientDefaultAccessTokenConverter()
accessTokenConverter.setUserTokenConverter(new UnifiedUserAuthenticationConverter()); //设置自定义的authentication转化器
converter.setAccessTokenConverter(accessTokenConverter);
return converter;
}
}
JWT暴露给tokenService使用的类是tokenStore,而tokenStore又依赖于accessTokenConvert和authenticationConvert完成token的生成和解析
注意:SpringSecurity是通过tokenService来解析和生成token的,tokenStore依赖于tokenService!!!
首先我们来看看JWT令牌在代码中的样子,先来看它的接口
OAuth2AccessToken
public interface OAuth2AccessToken {
String BEARER_TYPE = "Bearer";
String OAUTH2_TYPE = "OAuth2";
String ACCESS_TOKEN = "access_token";
String TOKEN_TYPE = "token_type";
String EXPIRES_IN = "expires_in";
String REFRESH_TOKEN = "refresh_token";
String SCOPE = "scope";
Map getAdditionalInformation();
Set getScope();
OAuth2RefreshToken getRefreshToken();
String getTokenType();
boolean isExpired();
Date getExpiration();
int getExpiresIn();
String getValue();
}
该接口定义了一些字段的名称,并且告诉我们token需要具备value(编码后的hash值),scope(范围)、expire(过期时间)以及一些额外信息additionalInformation。接下来我们再来看框架提供给我们的默认实现类
DefaultOAuth2AccessToken
public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {
private static final long serialVersionUID = 914967629530462926L;
private String value;
private Date expiration;
private String tokenType;
private OAuth2RefreshToken refreshToken;
private Set scope;
private Map additionalInformation;
public DefaultOAuth2AccessToken(String value) {
this.tokenType = "Bearer".toLowerCase();
this.additionalInformation = Collections.emptyMap();
this.value = value;
}
private DefaultOAuth2AccessToken() {
this((String)null);
}
public DefaultOAuth2AccessToken(OAuth2AccessToken accessToken) {
this(accessToken.getValue());
this.setAdditionalInformation(accessToken.getAdditionalInformation());
this.setRefreshToken(accessToken.getRefreshToken());
this.setExpiration(accessToken.getExpiration());
this.setScope(accessToken.getScope());
this.setTokenType(accessToken.getTokenType());
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public int getExpiresIn() {
return this.expiration != null ? Long.valueOf((this.expiration.getTime() - System.currentTimeMillis()) / 1000L).intValue() : 0;
}
protected void setExpiresIn(int delta) {
this.setExpiration(new Date(System.currentTimeMillis() + (long)delta));
}
public Date getExpiration() {
return this.expiration;
}
public void setExpiration(Date expiration) {
this.expiration = expiration;
}
public boolean isExpired() {
return this.expiration != null && this.expiration.before(new Date());
}
public String getTokenType() {
return this.tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public OAuth2RefreshToken getRefreshToken() {
return this.refreshToken;
}
public void setRefreshToken(OAuth2RefreshToken refreshToken) {
this.refreshToken = refreshToken;
}
public Set getScope() {
return this.scope;
}
public void setScope(Set scope) {
this.scope = scope;
}
public boolean equals(Object obj) {
return obj != null && this.toString().equals(obj.toString());
}
public int hashCode() {
return this.toString().hashCode();
}
public String toString() {
return String.valueOf(this.getValue());
}
public static OAuth2AccessToken valueOf(Map tokenParams) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken((String)tokenParams.get("access_token"));
if (tokenParams.containsKey("expires_in")) {
long expiration = 0L;
try {
expiration = Long.parseLong(String.valueOf(tokenParams.get("expires_in")));
} catch (NumberFormatException var5) {
}
token.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000L));
}
if (tokenParams.containsKey("refresh_token")) {
String refresh = (String)tokenParams.get("refresh_token");
DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken(refresh);
token.setRefreshToken(refreshToken);
}
if (tokenParams.containsKey("scope")) {
Set scope = new TreeSet();
StringTokenizer tokenizer = new StringTokenizer((String)tokenParams.get("scope"), " ,");
while(tokenizer.hasMoreTokens()) {
scope.add(tokenizer.nextToken());
}
token.setScope(scope);
}
if (tokenParams.containsKey("token_type")) {
token.setTokenType((String)tokenParams.get("token_type"));
}
return token;
}
public Map getAdditionalInformation() {
return this.additionalInformation;
}
public void setAdditionalInformation(Map additionalInformation) {
this.additionalInformation = new LinkedHashMap(additionalInformation);
}
}
令牌解析的目的是为了将token的编码转化为AccessToken,从而提取出authentication等信息
使用通过认证的authentication来生成令牌
```
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (!existingAccessToken.isExpired()) {
this.tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
this.tokenStore.removeRefreshToken(refreshToken);
}
this.tokenStore.removeAccessToken(existingAccessToken);
}
if (refreshToken == null) {
refreshToken = this.createRefreshToken(authentication);
} else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken)refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = this.createRefreshToken(authentication);
}
}
OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken);
this.tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
this.tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
```
**createAccessToken**
```
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (long)validitySeconds * 1000L));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return (OAuth2AccessToken)(this.accessTokenEnhancer != null ? this.accessTokenEnhancer.enhance(token, authentication) : token);
```
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
Map info = new LinkedHashMap(accessToken.getAdditionalInformation());
String tokenId = result.getValue();
if (!info.containsKey("jti")) {
info.put("jti", tokenId);
} else {
tokenId = (String)info.get("jti");
}
result.setAdditionalInformation(info);
//注意这一行
//将authentication的信息提取到result中,再对result编码
result.setValue(this.encode(result, authentication));
OAuth2RefreshToken refreshToken = result.getRefreshToken();
if (refreshToken != null) {
DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken);
encodedRefreshToken.setValue(refreshToken.getValue());
encodedRefreshToken.setExpiration((Date)null);
try {
Map claims = this.objectMapper.parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims());
if (claims.containsKey("jti")) {
encodedRefreshToken.setValue(claims.get("jti").toString());
}
} catch (IllegalArgumentException var11) {
}
Map refreshTokenInfo = new LinkedHashMap(accessToken.getAdditionalInformation());
refreshTokenInfo.put("jti", encodedRefreshToken.getValue());
refreshTokenInfo.put("ati", tokenId);
encodedRefreshToken.setAdditionalInformation(refreshTokenInfo);
DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(this.encode(encodedRefreshToken, authentication));
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
Date expiration = ((ExpiringOAuth2RefreshToken)refreshToken).getExpiration();
encodedRefreshToken.setExpiration(expiration);
token = new DefaultExpiringOAuth2RefreshToken(this.encode(encodedRefreshToken, authentication), expiration);
}
result.setRefreshToken((OAuth2RefreshToken)token);
}
return result;
}
encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication)
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
content = this.objectMapper.formatMap(this.tokenConverter.convertAccessToken(accessToken, authentication));
} catch (Exception var5) {
throw new IllegalStateException("Cannot convert access token to JSON", var5);
}
String token = JwtHelper.encode(content, this.signer).getEncoded();
return token;
}
该方法将authentication中需要被提取的信息封装成map返回,我们也可以重写该方法自定义返回的map来扩充JWT令牌
public Map convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
Map response = new HashMap();
OAuth2Request clientToken = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
response.putAll(this.userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication()));
} else if (clientToken.getAuthorities() != null && !clientToken.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(clientToken.getAuthorities()));
}
if (token.getScope() != null) {
response.put(this.scopeAttribute, token.getScope());
}
if (token.getAdditionalInformation().containsKey("jti")) {
response.put("jti", token.getAdditionalInformation().get("jti"));
}
if (token.getExpiration() != null) {
response.put("exp", token.getExpiration().getTime() / 1000L);
}
if (this.includeGrantType && authentication.getOAuth2Request().getGrantType() != null) {
response.put("grant_type", authentication.getOAuth2Request().getGrantType());
}
response.putAll(token.getAdditionalInformation());
response.put(this.clientIdAttribute, clientToken.getClientId());
if (clientToken.getResourceIds() != null && !clientToken.getResourceIds().isEmpty()) {
response.put("aud", clientToken.getResourceIds());
}
return response;
}