Skip to content

Commit 3b183f1

Browse files
Merge pull request #941 from exadel-inc/EFRS-1286_the_ability_to_delete_expired_access_and_refresh_tokens
EFRS-1286: Added the ability to delete expired access and refresh tokens
2 parents 2099962 + 58ad870 commit 3b183f1

File tree

11 files changed

+311
-15
lines changed

11 files changed

+311
-15
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.exadel.frs.system.security;
2+
3+
import static java.time.ZoneOffset.UTC;
4+
import java.sql.Types;
5+
import java.time.LocalDateTime;
6+
import javax.sql.DataSource;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.jdbc.core.JdbcTemplate;
9+
import org.springframework.jdbc.core.support.SqlLobValue;
10+
import org.springframework.scheduling.annotation.Scheduled;
11+
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
12+
import org.springframework.security.oauth2.common.OAuth2AccessToken;
13+
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
14+
import org.springframework.security.oauth2.provider.OAuth2Authentication;
15+
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
16+
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
17+
import org.springframework.stereotype.Component;
18+
import org.springframework.transaction.annotation.Transactional;
19+
20+
@Slf4j
21+
@Component
22+
public class CustomJdbcTokenStore extends JdbcTokenStore {
23+
24+
private static final String INSERT_ACCESS_TOKEN_WITH_EXPIRATION_SQL = "insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token, expiration) values (?, ?, ?, ?, ?, ?, ?,?)";
25+
private static final String INSERT_REFRESH_TOKEN_WITH_EXPIRATION_SQL = "insert into oauth_refresh_token (token_id, token, authentication, expiration) values (?, ?, ?, ?)";
26+
private static final String REMOVE_EXPIRED_ACCESS_TOKENS_SQL = "delete from oauth_access_token where expiration < ?";
27+
private static final String REMOVE_EXPIRED_REFRESH_TOKENS_SQL = "delete from oauth_refresh_token where expiration < ?";
28+
29+
private final JdbcTemplate jdbcTemplate;
30+
private final AuthenticationKeyGenerator authenticationKeyGenerator;
31+
32+
public CustomJdbcTokenStore(DataSource dataSource) {
33+
super(dataSource);
34+
this.jdbcTemplate = new JdbcTemplate(dataSource);
35+
this.authenticationKeyGenerator = new AuthenticationKeyGeneratorImpl();
36+
this.setAuthenticationKeyGenerator(this.authenticationKeyGenerator);
37+
}
38+
39+
@Override
40+
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
41+
String refreshToken = null;
42+
if (token.getRefreshToken() != null) {
43+
refreshToken = token.getRefreshToken().getValue();
44+
}
45+
46+
if (readAccessToken(token.getValue()) != null) {
47+
removeAccessToken(token.getValue());
48+
}
49+
50+
jdbcTemplate.update(INSERT_ACCESS_TOKEN_WITH_EXPIRATION_SQL, new Object[]{extractTokenKey(token.getValue()),
51+
new SqlLobValue(serializeAccessToken(token)), this.authenticationKeyGenerator.extractKey(authentication),
52+
authentication.isClientOnly() ? null : authentication.getName(),
53+
authentication.getOAuth2Request().getClientId(),
54+
new SqlLobValue(serializeAuthentication(authentication)), extractTokenKey(refreshToken),
55+
token.getExpiration().toInstant().atOffset(UTC).toLocalDateTime()},
56+
new int[]{Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.TIMESTAMP}
57+
);
58+
}
59+
60+
@Override
61+
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
62+
DefaultExpiringOAuth2RefreshToken oAuth2RefreshToken = (DefaultExpiringOAuth2RefreshToken) refreshToken;
63+
jdbcTemplate.update(INSERT_REFRESH_TOKEN_WITH_EXPIRATION_SQL, new Object[]{
64+
extractTokenKey(refreshToken.getValue()),
65+
new SqlLobValue(serializeRefreshToken(refreshToken)),
66+
new SqlLobValue(serializeAuthentication(authentication)),
67+
oAuth2RefreshToken.getExpiration().toInstant().atOffset(UTC).toLocalDateTime()},
68+
new int[]{Types.VARCHAR, Types.BLOB, Types.BLOB, Types.TIMESTAMP}
69+
);
70+
}
71+
72+
@Transactional
73+
@Scheduled(cron = "@weekly", zone = "UTC")
74+
public void removeExpiredTokens() {
75+
LocalDateTime now = LocalDateTime.now(UTC);
76+
int accessTokenCount = this.jdbcTemplate.update(
77+
REMOVE_EXPIRED_ACCESS_TOKENS_SQL,
78+
now
79+
);
80+
int refreshTokenCount = this.jdbcTemplate.update(
81+
REMOVE_EXPIRED_REFRESH_TOKENS_SQL,
82+
now
83+
);
84+
log.info(
85+
"Removed {} expired access tokens and {} expired update tokens",
86+
accessTokenCount,
87+
refreshTokenCount
88+
);
89+
}
90+
}

java/admin/src/main/java/com/exadel/frs/system/security/config/AuthServerConfig.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.exadel.frs.system.global.Constants.ADMIN;
2020
import static java.util.stream.Collectors.toList;
2121
import com.exadel.frs.system.security.AuthenticationKeyGeneratorImpl;
22+
import com.exadel.frs.system.security.CustomJdbcTokenStore;
2223
import com.exadel.frs.system.security.CustomOAuth2Exception;
2324
import com.exadel.frs.system.security.CustomUserDetailsService;
2425
import com.exadel.frs.system.security.TokenServicesImpl;
@@ -56,16 +57,9 @@ public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
5657
private final AuthenticationManager authenticationManager;
5758
private final ClientService clientService;
5859
private final CustomUserDetailsService userDetailsService;
59-
private final DataSource dataSource;
6060
private final PasswordEncoder passwordEncoder;
6161
private final OAuthClientProperties authClientProperties;
62-
63-
@Bean
64-
public JdbcTokenStore tokenStore() {
65-
JdbcTokenStore tokenStore = new JdbcTokenStore(dataSource);
66-
tokenStore.setAuthenticationKeyGenerator(new AuthenticationKeyGeneratorImpl());
67-
return tokenStore;
68-
}
62+
private final JdbcTokenStore tokenStore;
6963

7064
@Bean
7165
@Primary
@@ -82,7 +76,7 @@ public TokenEndpoint tokenEndpoint(AuthorizationServerEndpointsConfiguration con
8276

8377
@Bean
8478
public DefaultTokenServices tokenServices() {
85-
TokenServicesImpl tokenServices = new TokenServicesImpl(tokenStore());
79+
TokenServicesImpl tokenServices = new TokenServicesImpl(tokenStore);
8680
tokenServices.setClientDetailsService(clientService);
8781
return tokenServices;
8882
}
@@ -118,7 +112,7 @@ public void configure(final ClientDetailsServiceConfigurer clients) throws Excep
118112
@Override
119113
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
120114
endpoints
121-
.tokenStore(tokenStore())
115+
.tokenStore(tokenStore)
122116
.tokenServices(tokenServices())
123117
.authenticationManager(authenticationManager)
124118
.userDetailsService(userDetailsService)
@@ -135,4 +129,4 @@ public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
135129
}
136130
});
137131
}
138-
}
132+
}

java/admin/src/main/resources/application.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ spring:
5555
database: postgresql
5656
open-in-view: true
5757
generate-ddl: false
58+
liquibase:
59+
parameters:
60+
common-client:
61+
client-id: ${app.security.oauth2.clients.COMMON.client-id}
62+
access-token-validity: ${app.security.oauth2.clients.COMMON.access-token-validity}
63+
refresh-token-validity: ${app.security.oauth2.clients.COMMON.refresh-token-validity}
64+
authorized-grant-types: ${app.security.oauth2.clients.COMMON.authorized-grant-types}
5865
mail:
5966
enable: ${ENABLE_EMAIL_SERVER:false}
6067
host: ${EMAIL_HOST:example.com}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
databaseChangeLog:
2+
- changeSet:
3+
id: expand-oauth_access_token-and-oauth_refresh_token-with-expiration-column
4+
author: Volodymyr Bushko
5+
changes:
6+
# add an expiration column to the oauth_access_token & oauth_refresh_token tables
7+
- addColumn:
8+
tableName: oauth_access_token
9+
columns:
10+
- column:
11+
name: expiration
12+
type: timestamp
13+
- addColumn:
14+
tableName: oauth_refresh_token
15+
columns:
16+
- column:
17+
name: expiration
18+
type: timestamp
19+
# set the expiration columns in order to avoid conflicts
20+
- customChange: {
21+
"class": "com.exadel.frs.commonservice.system.liquibase.customchange.SetOAuthTokenExpirationCustomChange",
22+
"clientId": "${common-client.client-id}",
23+
"accessTokenValidity": "${common-client.access-token-validity}",
24+
"refreshTokenValidity": "${common-client.refresh-token-validity}",
25+
"authorizedGrantTypes": "${common-client.authorized-grant-types}"
26+
}
27+
# add a not-null constraint to the expiration columns
28+
- addNotNullConstraint:
29+
tableName: oauth_access_token
30+
columnName: expiration
31+
- addNotNullConstraint:
32+
tableName: oauth_refresh_token
33+
columnName: expiration

java/admin/src/main/resources/db/changelog/db.changelog-master.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ databaseChangeLog:
4747
file: db/changelog/db.changelog-0.2.3.yaml
4848
- include:
4949
file: db/changelog/db.changelog-0.2.4.yaml
50+
- include:
51+
file: db/changelog/db.changelog-0.2.5.yaml

java/admin/src/test/resources/application.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ spring:
4848
database: postgresql
4949
open-in-view: true
5050
generate-ddl: false
51+
liquibase:
52+
parameters:
53+
common-client:
54+
client-id: ${app.security.oauth2.clients.COMMON.client-id}
55+
access-token-validity: ${app.security.oauth2.clients.COMMON.access-token-validity}
56+
refresh-token-validity: ${app.security.oauth2.clients.COMMON.refresh-token-validity}
57+
authorized-grant-types: ${app.security.oauth2.clients.COMMON.authorized-grant-types}
5158
mail:
5259
5360

java/api/src/test/java/com/exadel/frs/core/trainservice/EmbeddedPostgreSQLTest.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.exadel.frs.core.trainservice.config.IntegrationTest;
44
import io.zonky.test.db.AutoConfigureEmbeddedDatabase;
5+
import javax.annotation.PostConstruct;
6+
import javax.sql.DataSource;
57
import liquibase.Contexts;
68
import liquibase.LabelExpression;
79
import liquibase.Liquibase;
@@ -10,12 +12,10 @@
1012
import liquibase.integration.spring.SpringResourceAccessor;
1113
import org.junit.jupiter.api.extension.ExtendWith;
1214
import org.springframework.beans.factory.annotation.Autowired;
15+
import org.springframework.core.env.Environment;
1316
import org.springframework.core.io.ResourceLoader;
1417
import org.springframework.test.context.junit.jupiter.SpringExtension;
1518

16-
import javax.annotation.PostConstruct;
17-
import javax.sql.DataSource;
18-
1919
@IntegrationTest
2020
@ExtendWith(SpringExtension.class)
2121
@AutoConfigureEmbeddedDatabase(beanName = "dsPg")
@@ -27,6 +27,9 @@ public class EmbeddedPostgreSQLTest {
2727
@Autowired
2828
ResourceLoader resourceLoader;
2929

30+
@Autowired
31+
private Environment env;
32+
3033
@PostConstruct
3134
public void initDatabase() {
3235
try {
@@ -35,10 +38,23 @@ public void initDatabase() {
3538
new SpringResourceAccessor(resourceLoader),
3639
DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(dataSource.getConnection()))
3740
);
41+
setLiquibaseChangeLogParams(liquibase);
3842
liquibase.update(new Contexts(), new LabelExpression());
3943
} catch (Exception e) {
4044
//manage exception
4145
e.printStackTrace();
4246
}
4347
}
48+
49+
private void setLiquibaseChangeLogParams(final Liquibase liquibase) {
50+
String clientId = env.getProperty("spring.liquibase.parameters.common-client.client-id", "CommonClientId");
51+
String accessTokenValidity = env.getProperty("spring.liquibase.parameters.common-client.access-token-validity", "2400");
52+
String refreshTokenValidity = env.getProperty("spring.liquibase.parameters.common-client.refresh-token-validity", "1209600");
53+
String authorizedGrantTypes = env.getProperty("spring.liquibase.parameters.common-client.authorized-grant-types", "password,refresh_token");
54+
55+
liquibase.setChangeLogParameter("common-client.client-id", clientId);
56+
liquibase.setChangeLogParameter("common-client.access-token-validity", accessTokenValidity);
57+
liquibase.setChangeLogParameter("common-client.refresh-token-validity", refreshTokenValidity);
58+
liquibase.setChangeLogParameter("common-client.authorized-grant-types", authorizedGrantTypes);
59+
}
4460
}

java/api/src/test/resources/application.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ spring:
55
enabled: false
66
liquibase:
77
enabled: false
8+
parameters:
9+
common-client:
10+
client-id: CommonClientId
11+
access-token-validity: 2400
12+
refresh-token-validity: 1209600
13+
authorized-grant-types: password,refresh_token
814
datasource-pg:
915
driver-class-name: org.postgresql.Driver
1016
url: ${POSTGRES_URL:jdbc:postgresql://localhost:5432/frs_test}
@@ -62,4 +68,4 @@ logging:
6268
level:
6369
org.hibernate.SQL: DEBUG
6470
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
65-
org.hibernate.type: TRACE
71+
org.hibernate.type: TRACE
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
databaseChangeLog:
2+
- changeSet:
3+
id: expand-oauth_access_token-and-oauth_refresh_token-with-expiration-column
4+
author: Volodymyr Bushko
5+
changes:
6+
# add an expiration column to the oauth_access_token & oauth_refresh_token tables
7+
- addColumn:
8+
tableName: oauth_access_token
9+
columns:
10+
- column:
11+
name: expiration
12+
type: timestamp
13+
- addColumn:
14+
tableName: oauth_refresh_token
15+
columns:
16+
- column:
17+
name: expiration
18+
type: timestamp
19+
# set the expiration columns in order to avoid conflicts
20+
- customChange: {
21+
"class": "com.exadel.frs.commonservice.system.liquibase.customchange.SetOAuthTokenExpirationCustomChange",
22+
"clientId": "${common-client.client-id}",
23+
"accessTokenValidity": "${common-client.access-token-validity}",
24+
"refreshTokenValidity": "${common-client.refresh-token-validity}",
25+
"authorizedGrantTypes": "${common-client.authorized-grant-types}"
26+
}
27+
# add a not-null constraint to the expiration columns
28+
- addNotNullConstraint:
29+
tableName: oauth_access_token
30+
columnName: expiration
31+
- addNotNullConstraint:
32+
tableName: oauth_refresh_token
33+
columnName: expiration

java/api/src/test/resources/db/changelog/db.changelog-master.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ databaseChangeLog:
4747
file: db/changelog/db.changelog-0.2.3.yaml
4848
- include:
4949
file: db/changelog/db.changelog-0.2.4.yaml
50+
- include:
51+
file: db/changelog/db.changelog-0.2.5.yaml

0 commit comments

Comments
 (0)