CustomRedisTokenStore.java 22.2 KB
Newer Older
1 2
package com.central.oauth2.common.store;

3 4
import com.central.common.constant.SecurityConstants;
import com.central.oauth2.common.properties.SecurityProperties;
5 6
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
7
import org.springframework.data.redis.serializer.RedisSerializer;
8
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
9 10 11
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
12
import org.springframework.security.oauth2.provider.ClientDetails;
13
import org.springframework.security.oauth2.provider.OAuth2Authentication;
14
import org.springframework.security.oauth2.provider.OAuth2Request;
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * 优化自Spring Security的RedisTokenStore
zlt2000's avatar
zlt2000 已提交
33 34
 * 1. 支持redis所有集群模式包括cluster模式
 * 2. 使用pipeline减少连接次数,提升性能
35
 * 3. 自动续签token(可配置是否开启)
36 37 38 39 40 41
 *
 * @author zlt
 * @date 2019/7/7
 */
public class CustomRedisTokenStore implements TokenStore {
    private static final String ACCESS = "access:";
L
lpphan 已提交
42
    private static final String ACCESS_BAK = "access_bak:";
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
    private static final String AUTH_TO_ACCESS = "auth_to_access:";
    private static final String REFRESH_AUTH = "refresh_auth:";
    private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
    private static final String REFRESH = "refresh:";
    private static final String REFRESH_TO_ACCESS = "refresh_to_access:";

    private static final boolean springDataRedis_2_0 = ClassUtils.isPresent(
            "org.springframework.data.redis.connection.RedisStandaloneConfiguration",
            RedisTokenStore.class.getClassLoader());

    private final RedisConnectionFactory connectionFactory;
    private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
    private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();

    private String prefix = "";

    private Method redisConnectionSet_2_0;

61 62 63 64 65
    /**
     * 认证配置
     */
    private SecurityProperties securityProperties;

66 67 68 69 70 71
    /**
     * 业务redis的value序列化
     */
    private RedisSerializer<Object> redisValueSerializer;

    public CustomRedisTokenStore(RedisConnectionFactory connectionFactory, SecurityProperties securityProperties, RedisSerializer<Object> redisValueSerializer) {
72
        this.connectionFactory = connectionFactory;
73
        this.securityProperties = securityProperties;
74
        this.redisValueSerializer = redisValueSerializer;
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
        if (springDataRedis_2_0) {
            this.loadRedisConnectionMethods_2_0();
        }
    }

    public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
        this.authenticationKeyGenerator = authenticationKeyGenerator;
    }

    public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {
        this.serializationStrategy = serializationStrategy;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    private void loadRedisConnectionMethods_2_0() {
        this.redisConnectionSet_2_0 = ReflectionUtils.findMethod(
                RedisConnection.class, "set", byte[].class, byte[].class);
    }

    private RedisConnection getConnection() {
        return connectionFactory.getConnection();
    }

    private byte[] serialize(Object object) {
        return serializationStrategy.serialize(object);
    }

    private byte[] serializeKey(String object) {
        return serialize(prefix + object);
    }

    private OAuth2AccessToken deserializeAccessToken(byte[] bytes) {
        return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);
    }

    private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
        return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
    }

    private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) {
        return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);
    }

121
    private ClientDetails deserializeClientDetails(byte[] bytes) {
L
lpphan 已提交
122
        return (ClientDetails) redisValueSerializer.deserialize(bytes);
123 124
    }

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    private byte[] serialize(String string) {
        return serializationStrategy.serialize(string);
    }

    private String deserializeString(byte[] bytes) {
        return serializationStrategy.deserializeString(bytes);
    }

    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        String key = authenticationKeyGenerator.extractKey(authentication);
        byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
        byte[] bytes;
        RedisConnection conn = getConnection();
        try {
            bytes = conn.get(serializedKey);
        } finally {
            conn.close();
        }
        OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
        if (accessToken != null) {
            OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
            if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) {
                // Keep the stores consistent (maybe the same user is
                // represented by this authentication but the details have
                // changed)
                storeAccessToken(accessToken, authentication);
            }

        }
        return accessToken;
    }

    @Override
    public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
160 161 162 163 164 165 166 167 168 169
        OAuth2Authentication auth2Authentication = readAuthentication(token.getValue());
        //是否开启token续签
        boolean isRenew = securityProperties.getAuth().getRenew().getEnable();
        if (isRenew && auth2Authentication != null) {
            OAuth2Request clientAuth = auth2Authentication.getOAuth2Request();
            //判断当前应用是否需要自动续签
            if (checkRenewClientId(clientAuth.getClientId())) {
                //获取过期时长
                int validitySeconds = getAccessTokenValiditySeconds(clientAuth.getClientId());
                if (validitySeconds > 0) {
L
lpphan 已提交
170
                    double expiresRatio = token.getExpiresIn() / (double) validitySeconds;
171
                    //判断是否需要续签,当前剩余时间小于过期时长的50%则续签
172
                    if (expiresRatio <= securityProperties.getAuth().getRenew().getTimeRatio()) {
173 174 175 176 177 178 179 180 181 182 183 184 185
                        //更新AccessToken过期时间
                        DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token;
                        oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
                        storeAccessToken(oAuth2AccessToken, auth2Authentication, true);
                    }
                }
            }
        }
        return auth2Authentication;
    }

    /**
     * 判断应用自动续签是否满足白名单和黑名单的过滤逻辑
L
lpphan 已提交
186
     *
187 188 189 190 191 192 193 194 195 196 197
     * @param clientId 应用id
     * @return 是否满足
     */
    private boolean checkRenewClientId(String clientId) {
        boolean result = true;
        //白名单
        List<String> includeClientIds = securityProperties.getAuth().getRenew().getIncludeClientIds();
        //黑名单
        List<String> exclusiveClientIds = securityProperties.getAuth().getRenew().getExclusiveClientIds();
        if (includeClientIds.size() > 0) {
            result = includeClientIds.contains(clientId);
L
lpphan 已提交
198
        } else if (exclusiveClientIds.size() > 0) {
199 200 201 202 203 204 205
            result = !exclusiveClientIds.contains(clientId);
        }
        return result;
    }

    /**
     * 获取token的总有效时长
L
lpphan 已提交
206
     *
207 208 209 210 211 212 213 214 215 216
     * @param clientId 应用id
     */
    private int getAccessTokenValiditySeconds(String clientId) {
        RedisConnection conn = getConnection();
        byte[] bytes;
        try {
            bytes = conn.get(serializeKey(SecurityConstants.CACHE_CLIENT_KEY + ":" + clientId));
        } finally {
            conn.close();
        }
zlt2000's avatar
zlt2000 已提交
217 218 219 220 221
        if (bytes != null) {
            ClientDetails clientDetails = deserializeClientDetails(bytes);
            if (clientDetails.getAccessTokenValiditySeconds() != null) {
                return clientDetails.getAccessTokenValiditySeconds();
            }
222
        }
zlt2000's avatar
zlt2000 已提交
223

224 225
        //返回默认值
        return SecurityConstants.ACCESS_TOKEN_VALIDITY_SECONDS;
226 227 228 229 230 231 232
    }

    @Override
    public OAuth2Authentication readAuthentication(String token) {
        byte[] bytes;
        RedisConnection conn = getConnection();
        try {
233
            bytes = conn.get(serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token));
234 235 236
        } finally {
            conn.close();
        }
237
        return deserializeAuthentication(bytes);
238 239 240 241 242 243 244 245 246 247 248
    }

    @Override
    public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
        return readAuthenticationForRefreshToken(token.getValue());
    }

    public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
        RedisConnection conn = getConnection();
        try {
            byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));
249
            return deserializeAuthentication(bytes);
250 251 252 253 254 255 256
        } finally {
            conn.close();
        }
    }

    @Override
    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
257 258 259 260 261
        storeAccessToken(token, authentication, false);
    }

    /**
     * 存储token
L
lpphan 已提交
262
     *
zlt2000's avatar
zlt2000 已提交
263
     * @param isRenew 是否续签
264
     */
zlt2000's avatar
zlt2000 已提交
265
    private void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication, boolean isRenew) {
266 267 268
        byte[] serializedAccessToken = serialize(token);
        byte[] serializedAuth = serialize(authentication);
        byte[] accessKey = serializeKey(ACCESS + token.getValue());
L
lpphan 已提交
269
        byte[] accessBakKey = serializeKey(ACCESS_BAK + token.getValue());
270
        byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token.getValue());
271
        byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
272 273
        byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
        byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
274 275 276

        RedisConnection conn = getConnection();
        try {
277 278 279 280
            byte[] oldAccessToken = conn.get(accessKey);
            //如果token已存在,并且不是续签的话直接返回
            if (!isRenew && oldAccessToken != null) {
                return;
zlt2000's avatar
zlt2000 已提交
281 282
            }

zlt2000's avatar
zlt2000 已提交
283
            conn.openPipeline();
284 285 286
            if (springDataRedis_2_0) {
                try {
                    this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken);
L
lpphan 已提交
287
                    this.redisConnectionSet_2_0.invoke(conn, accessBakKey, serializedAccessToken);
288 289 290 291 292 293 294
                    this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth);
                    this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            } else {
                conn.set(accessKey, serializedAccessToken);
L
lpphan 已提交
295
                conn.set(accessBakKey, serializedAccessToken);
296 297 298
                conn.set(authKey, serializedAuth);
                conn.set(authToAccessKey, serializedAccessToken);
            }
zlt2000's avatar
zlt2000 已提交
299 300
            //如果是续签token,需要先删除集合里旧的值
            if (oldAccessToken != null) {
301
                if (!authentication.isClientOnly()) {
302
                    conn.lRem(approvalKey, 1, oldAccessToken);
303
                }
zlt2000's avatar
zlt2000 已提交
304 305 306
                conn.lRem(clientId, 1, oldAccessToken);
            }
            if (!authentication.isClientOnly()) {
307
                conn.rPush(approvalKey, serializedAccessToken);
308
            }
zlt2000's avatar
zlt2000 已提交
309
            conn.rPush(clientId, serializedAccessToken);
310 311 312
            if (token.getExpiration() != null) {
                int seconds = token.getExpiresIn();
                conn.expire(accessKey, seconds);
L
lpphan 已提交
313
                conn.expire(accessBakKey, seconds + 60);
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
                conn.expire(authKey, seconds);
                conn.expire(authToAccessKey, seconds);
                conn.expire(clientId, seconds);
                conn.expire(approvalKey, seconds);
            }
            OAuth2RefreshToken refreshToken = token.getRefreshToken();
            if (refreshToken != null && refreshToken.getValue() != null) {
                byte[] refresh = serialize(token.getRefreshToken().getValue());
                byte[] auth = serialize(token.getValue());
                byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());
                byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());
                if (springDataRedis_2_0) {
                    try {
                        this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, auth);
                        this.redisConnectionSet_2_0.invoke(conn, accessToRefreshKey, refresh);
                    } catch (Exception ex) {
                        throw new RuntimeException(ex);
                    }
                } else {
                    conn.set(refreshToAccessKey, auth);
                    conn.set(accessToRefreshKey, refresh);
                }
                expireRefreshToken(refreshToken, conn, refreshToAccessKey, accessToRefreshKey);
            }
zlt2000's avatar
zlt2000 已提交
338
            conn.closePipeline();
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
        } finally {
            conn.close();
        }
    }

    private static String getApprovalKey(OAuth2Authentication authentication) {
        String userName = authentication.getUserAuthentication() == null ? ""
                : authentication.getUserAuthentication().getName();
        return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
    }

    private static String getApprovalKey(String clientId, String userName) {
        return clientId + (userName == null ? "" : ":" + userName);
    }

    @Override
    public void removeAccessToken(OAuth2AccessToken accessToken) {
        removeAccessToken(accessToken.getValue());
    }

    @Override
    public OAuth2AccessToken readAccessToken(String tokenValue) {
        byte[] key = serializeKey(ACCESS + tokenValue);
        byte[] bytes;
        RedisConnection conn = getConnection();
        try {
            bytes = conn.get(key);
        } finally {
            conn.close();
        }
        return deserializeAccessToken(bytes);
    }

    public void removeAccessToken(String tokenValue) {
        byte[] accessKey = serializeKey(ACCESS + tokenValue);
L
lpphan 已提交
374
        byte[] accessBakKey = serializeKey(ACCESS_BAK + tokenValue);
375
        byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + tokenValue);
376 377 378
        byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
        RedisConnection conn = getConnection();
        try {
379 380
            byte[] access = conn.get(accessKey);
            byte[] auth = conn.get(authKey);
zlt2000's avatar
zlt2000 已提交
381
            conn.openPipeline();
382
            conn.del(accessKey);
L
lpphan 已提交
383
            conn.del(accessBakKey);
384 385 386
            conn.del(accessToRefreshKey);
            // Don't remove the refresh token - it's up to the caller to do that
            conn.del(authKey);
387
            conn.closePipeline();
388 389 390 391 392

            OAuth2Authentication authentication = deserializeAuthentication(auth);
            if (authentication != null) {
                String key = authenticationKeyGenerator.extractKey(authentication);
                byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);
393 394
                byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
                byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
zlt2000's avatar
zlt2000 已提交
395
                conn.openPipeline();
396
                conn.del(authToAccessKey);
397
                conn.lRem(unameKey, 1, access);
398 399
                conn.lRem(clientId, 1, access);
                conn.del(serialize(ACCESS + key));
zlt2000's avatar
zlt2000 已提交
400
                conn.closePipeline();
401 402 403 404 405 406 407 408 409 410 411 412 413
            }
        } finally {
            conn.close();
        }
    }

    @Override
    public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
        byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue());
        byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue());
        byte[] serializedRefreshToken = serialize(refreshToken);
        RedisConnection conn = getConnection();
        try {
zlt2000's avatar
zlt2000 已提交
414
            conn.openPipeline();
415 416 417 418 419 420 421 422 423 424 425 426
            if (springDataRedis_2_0) {
                try {
                    this.redisConnectionSet_2_0.invoke(conn, refreshKey, serializedRefreshToken);
                    this.redisConnectionSet_2_0.invoke(conn, refreshAuthKey, serialize(authentication));
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            } else {
                conn.set(refreshKey, serializedRefreshToken);
                conn.set(refreshAuthKey, serialize(authentication));
            }
            expireRefreshToken(refreshToken, conn, refreshKey, refreshAuthKey);
zlt2000's avatar
zlt2000 已提交
427
            conn.closePipeline();
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
        } finally {
            conn.close();
        }
    }

    private void expireRefreshToken(OAuth2RefreshToken refreshToken, RedisConnection conn, byte[] refreshKey, byte[] refreshAuthKey) {
        if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
            ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
            Date expiration = expiringRefreshToken.getExpiration();
            if (expiration != null) {
                int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
                        .intValue();
                conn.expire(refreshKey, seconds);
                conn.expire(refreshAuthKey, seconds);
            }
        }
    }

    @Override
    public OAuth2RefreshToken readRefreshToken(String tokenValue) {
        byte[] key = serializeKey(REFRESH + tokenValue);
        byte[] bytes;
        RedisConnection conn = getConnection();
        try {
            bytes = conn.get(key);
        } finally {
            conn.close();
        }
456
        return deserializeRefreshToken(bytes);
457 458 459 460 461 462 463 464 465 466 467 468 469 470
    }

    @Override
    public void removeRefreshToken(OAuth2RefreshToken refreshToken) {
        removeRefreshToken(refreshToken.getValue());
    }

    public void removeRefreshToken(String tokenValue) {
        byte[] refreshKey = serializeKey(REFRESH + tokenValue);
        byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue);
        byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue);
        byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
        RedisConnection conn = getConnection();
        try {
zlt2000's avatar
zlt2000 已提交
471
            conn.openPipeline();
472 473 474 475
            conn.del(refreshKey);
            conn.del(refreshAuthKey);
            conn.del(refresh2AccessKey);
            conn.del(access2RefreshKey);
zlt2000's avatar
zlt2000 已提交
476
            conn.closePipeline();
477 478 479 480 481 482 483 484 485 486 487 488 489
        } finally {
            conn.close();
        }
    }

    @Override
    public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
        removeAccessTokenUsingRefreshToken(refreshToken.getValue());
    }

    private void removeAccessTokenUsingRefreshToken(String refreshToken) {
        byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken);
        RedisConnection conn = getConnection();
490
        byte[] bytes = null;
491
        try {
492
            bytes = conn.get(key);
493 494 495 496
            conn.del(key);
        } finally {
            conn.close();
        }
497
        if (bytes == null) {
498 499 500 501 502 503 504 505 506 507
            return;
        }
        String accessToken = deserializeString(bytes);
        if (accessToken != null) {
            removeAccessToken(accessToken);
        }
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
508
        byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(clientId, userName));
509 510 511
        List<byte[]> byteList;
        RedisConnection conn = getConnection();
        try {
512
            byteList = conn.lRange(approvalKey, 0, -1);
513 514 515 516 517 518 519 520
        } finally {
            conn.close();
        }
        return getTokenCollections(byteList);
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
521
        byte[] key = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + clientId);
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
        List<byte[]> byteList;
        RedisConnection conn = getConnection();
        try {
            byteList = conn.lRange(key, 0, -1);
        } finally {
            conn.close();
        }
        return getTokenCollections(byteList);
    }

    private Collection<OAuth2AccessToken> getTokenCollections(List<byte[]> byteList) {
        if (byteList == null || byteList.size() == 0) {
            return Collections.emptySet();
        }
        List<OAuth2AccessToken> accessTokens = new ArrayList<>(byteList.size());
        for (byte[] bytes : byteList) {
            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
            accessTokens.add(accessToken);
        }
        return Collections.unmodifiableCollection(accessTokens);
    }
}