Skip to content

Commit

Permalink
Use refresh token to avoid re-login after 1 hour
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 committed Nov 10, 2024
1 parent 0d6bc69 commit e4e2403
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 62 deletions.
9 changes: 3 additions & 6 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ dependencies {
implementation("org.jetbrains:annotations:26.0.1")
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.6.8")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.3.5")
implementation("javax.inject:javax.inject:1")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
Expand Down Expand Up @@ -124,7 +123,7 @@ public void authorize(HydraAuthorizedEvent event) {

try {
meResult = getOne("/me", MeResult.class);
} catch (OAuth2AccessDeniedException e) {
} catch (Exception e) {
log.error("login failed", e);
return;
}
Expand Down Expand Up @@ -220,19 +219,16 @@ public void delete(ElideNavigatorOnId<?> navigator) {
}
}

@SuppressWarnings("unchecked")
@SneakyThrows
public <T extends ElideEntity> T getOne(ElideNavigatorOnId<T> navigator) {
return getOne(navigator.build(), navigator.getDtoClass(), Collections.emptyMap());
}

@SuppressWarnings("unchecked")
@SneakyThrows
public <T extends ElideEntity> T getOne(String endpointPath, Class<T> type) {
return getOne(endpointPath, type, Collections.emptyMap());
}

@SuppressWarnings("unchecked")
@SneakyThrows
public <T extends ElideEntity> T getOne(String endpointPath, Class<T> type, java.util.Map<String, Serializable> params) {
cycleAvoidingMappingContext.clearCache();
Expand Down
141 changes: 95 additions & 46 deletions src/main/java/com/faforever/moderatorclient/api/TokenService.java
Original file line number Diff line number Diff line change
@@ -1,77 +1,126 @@
package com.faforever.moderatorclient.api;

import com.faforever.moderatorclient.api.event.HydraAuthorizedEvent;
import com.faforever.moderatorclient.api.event.TokenExpiredEvent;
import com.faforever.moderatorclient.config.EnvironmentProperties;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class TokenService {
private final ApplicationEventPublisher applicationEventPublisher;
private RestTemplate restTemplate;
private EnvironmentProperties environmentProperties;
private OAuth2AccessToken tokenCache;

public TokenService(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

public void prepare(EnvironmentProperties environmentProperties) {
this.environmentProperties = environmentProperties;
this.restTemplate = new RestTemplateBuilder()
.requestFactory(JdkClientHttpRequestFactory.class)
.rootUri(environmentProperties.getOauthBaseUrl())
.build();
}

@SneakyThrows
public String getRefreshedTokenValue() {
if (tokenCache == null || tokenCache.isExpired()) {
log.info("Token expired, requesting new login");
applicationEventPublisher.publishEvent(new TokenExpiredEvent());
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
private final ApplicationEventPublisher applicationEventPublisher;
private RestTemplate restTemplate;
private EnvironmentProperties environmentProperties;
private OAuth2AccessTokenResponse tokenCache;

public TokenService(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

public void prepare(EnvironmentProperties environmentProperties) {
this.environmentProperties = environmentProperties;
this.restTemplate = new RestTemplateBuilder()
.requestFactory(JdkClientHttpRequestFactory.class)
.rootUri(environmentProperties.getOauthBaseUrl())
.build();
}

@SneakyThrows
public String getRefreshedTokenValue() {
if (tokenCache.getAccessToken().getExpiresAt().isBefore(Instant.now())) {
log.info("Token expired, requesting new with refresh token");
loginWithRefreshToken(tokenCache.getRefreshToken().getTokenValue(), false);
} else {
log.debug("Token still valid for {} seconds", Duration.between(Instant.now(), tokenCache.getAccessToken().getExpiresAt()));
}

return tokenCache.getAccessToken().getTokenValue();
}

public void loginWithAuthorizationCode(String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("code", code);
map.add("client_id", environmentProperties.getClientId());
map.add("redirect_uri", environmentProperties.getOauthRedirectUrl());
map.add("grant_type", "authorization_code");

Map<String, Object> responseBody = requestToken(headers, map);
if (responseBody != null) {
parseResponse(responseBody);

applicationEventPublisher.publishEvent(new HydraAuthorizedEvent());
}

}

return tokenCache.getValue();
}
private void parseResponse(Map<String, Object> responseBody) {
String accessToken = (String) responseBody.get("access_token");
String refreshToken = (String) responseBody.get("refresh_token");
Long expiresIn = Long.valueOf(responseBody.get("expires_in").toString());

tokenCache = OAuth2AccessTokenResponse.withToken(accessToken)
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.refreshToken(refreshToken)
.expiresIn(expiresIn)
.build();
}

public void loginWithAuthorizationCode(String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(List.of(MediaType.APPLICATION_JSON_UTF8));
public void loginWithRefreshToken(String refreshToken, boolean fireEvent) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("code", code);
map.add("client_id", environmentProperties.getClientId());
map.add("redirect_uri", environmentProperties.getOauthRedirectUrl());
map.add("grant_type", "authorization_code");
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("refresh_token", refreshToken);
map.add("client_id", environmentProperties.getClientId());
map.add("grant_type", "refresh_token");

Map<String, Object> responseBody = requestToken(headers, map);

if (responseBody != null) {
parseResponse(responseBody);

if (fireEvent) {
applicationEventPublisher.publishEvent(new HydraAuthorizedEvent());
}
}
}

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
private Map<String, Object> requestToken(HttpHeaders headers, MultiValueMap<String, String> map) {
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

tokenCache = restTemplate.postForObject(
"/oauth2/token",
request,
OAuth2AccessToken.class
);
ResponseEntity<Map<String, Object>> responseEntity = restTemplate.exchange(
"/oauth2/token",
HttpMethod.POST,
request,
new ParameterizedTypeReference<>() {
}
);

if (tokenCache != null) {
applicationEventPublisher.publishEvent(new HydraAuthorizedEvent());
return responseEntity.getBody();
}
}
}
6 changes: 3 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ faforever:
replay-download-url-format: https://replay.faforever.com/%s
oauth-base-url: https://hydra.faforever.com
oauth-redirect-url: http://localhost
oauth-scopes: upload_avatar administrative_actions read_sensible_userdata manage_vault
oauth-scopes: offline upload_avatar administrative_actions read_sensible_userdata manage_vault
user-base-url: https://user.faforever.com
"[test.faforever.com]":
base-url: https://api.test.faforever.com
client-id: faf-moderator-client
replay-download-url-format: https://replay.test.faforever.com/%s
oauth-base-url: https://hydra.test.faforever.com
oauth-redirect-url: http://localhost
oauth-scopes: upload_avatar administrative_actions read_sensible_userdata manage_vault
oauth-scopes: offline upload_avatar administrative_actions read_sensible_userdata manage_vault
user-base-url: https://user.test.faforever.com
"[localhost:8010]":
base-url: http://127.0.0.1:8010
client-id: faf-moderator-client
replay-download-url-format: https://replay.test.faforever.com/%s
oauth-base-url: http://localhost:4444
oauth-redirect-url: http://127.0.0.1
oauth-scopes: upload_avatar administrative_actions read_sensible_userdata manage_vault
oauth-scopes: offline upload_avatar administrative_actions read_sensible_userdata manage_vault
user-base-url: http://localhost:8080

logging:
Expand Down

0 comments on commit e4e2403

Please sign in to comment.