赞
踩
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<String, Object> getAdditionalInformation(); Set<String> 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<String> scope; private Map<String, Object> 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<String> getScope() { return this.scope; } public void setScope(Set<String> 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<String, String> 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<String> 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<String, Object> getAdditionalInformation() { return this.additionalInformation; } public void setAdditionalInformation(Map<String, Object> 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<String, Object> 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<String, Object> claims = this.objectMapper.parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims()); if (claims.containsKey("jti")) { encodedRefreshToken.setValue(claims.get("jti").toString()); } } catch (IllegalArgumentException var11) { } Map<String, Object> 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<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { Map<String, Object> 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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。