Skip to content

Commit

Permalink
Spring boot 3 upgrade (#4)
Browse files Browse the repository at this point in the history
* upgrade for support of spring boot 3

* upgrade to spring boot 3.3.5

* convert to Record & add JsonProperties

* upgrade gradle wrapper

* use Configuration annotation

* add ConditionalOnProperty directly

* delete OAuthConfig

* Change tests from Groovy/Spock into Java/Junit

* remove conditioal property

* set expiresIn to unixTimeStampInSeconds

* fix test to make sure authToken is UnixTimeStampInSeconds

* change expired logic to use Duration between and set buffer to 30 seconds

* remove unused restClient parameter

* change variable name

* fix test

* correctly set conditionalProperty

* get RestClient in constructor

* fix test

* remove durable debug logging

* change variable name and formatting

* create test to get new token when it has expired

* create default restclient

* remove unused dependency

* change property value

* change version

* refactor

* throw IllegalStateException if token fetching failed

* ignore a test

* yes tired sry

---------

Co-authored-by: Hknots <[email protected]>
trondsevre and hknots authored Dec 18, 2024
1 parent 6ae3d9c commit 6671b71
Showing 22 changed files with 436 additions and 329 deletions.
20 changes: 10 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import org.springframework.boot.gradle.plugin.SpringBootPlugin

plugins {
id 'org.springframework.boot' version '2.7.5' apply false
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'groovy'
id 'org.springframework.boot' version '3.3.5' apply false
id 'io.spring.dependency-management' version '1.1.6'
id 'java'
id 'maven-publish'
id 'java-library'
}

group = 'no.fintlabs'

sourceCompatibility = '17'

java {
sourceCompatibility = JavaVersion.VERSION_17
}

publishing {
publications {
@@ -39,15 +39,15 @@ dependencyManagement {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-web:3.3.5'

implementation 'org.yaml:snakeyaml'
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.5.2.RELEASE'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'cglib:cglib-nodep:3.3.0'
testImplementation 'org.spockframework:spock-spring:2.3-groovy-3.0'
testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0'
testImplementation 'org.spockframework:spock-spring:2.4-M4-groovy-4.0'
testImplementation 'org.spockframework:spock-core:2.4-M4-groovy-4.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = '1.3.0-SNAPSHOT'
version=1.3.0-SNAPSHOT
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
21 changes: 21 additions & 0 deletions src/main/java/no/fint/oauth/AuthToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package no.fint.oauth;

import com.fasterxml.jackson.annotation.JsonProperty;

public record AuthToken(
@JsonProperty("access_token") String accessToken,
@JsonProperty("token_type") String tokenType,
@JsonProperty("expires_in") long expirationTimestampMillis,
@JsonProperty("acr") String acr,
@JsonProperty("scope") String scope
) {

public AuthToken(String accessToken, String tokenType, long expirationTimestampMillis, String acr, String scope) {
this.accessToken = accessToken;
this.tokenType = tokenType;
this.expirationTimestampMillis = System.currentTimeMillis() + (expirationTimestampMillis * 1000);
this.acr = acr;
this.scope = scope;
}

}
48 changes: 0 additions & 48 deletions src/main/java/no/fint/oauth/OAuthConfig.java

This file was deleted.

26 changes: 0 additions & 26 deletions src/main/java/no/fint/oauth/OAuthRestTemplateFactory.java

This file was deleted.

10 changes: 5 additions & 5 deletions src/main/java/no/fint/oauth/OAuthTokenProps.java
Original file line number Diff line number Diff line change
@@ -4,23 +4,23 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;

@Getter
@Component
@Setter
@ToString
@Configuration
@JsonIgnoreProperties(ignoreUnknown = true)
public class OAuthTokenProps {

public static final String ENABLE_OAUTH = "fint.oauth.enabled";

@Value("${fint.oauth.username:}")
@JsonProperty
private String username;
81 changes: 81 additions & 0 deletions src/main/java/no/fint/oauth/TokenInstance.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package no.fint.oauth;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestClient;

import java.time.Duration;
import java.time.Instant;

@Slf4j
@Component
@ConditionalOnProperty(value = "fint.oauth.enabled", havingValue = "true")
public class TokenInstance {

private final RestClient oauthRestClient;
private final OAuthTokenProps props;
private final MultiValueMap<String, String> formData;
private AuthToken authToken;

public TokenInstance(OAuthTokenProps props, RestClient oauthRestClient) {
this.props = props;
this.oauthRestClient = oauthRestClient;
this.formData = createFormData();
}

@PostConstruct
public void init() {
if (ObjectUtils.isEmpty(props.getAccessTokenUri())) {
log.info("No Access-token-url configured, will not initialize access token");
} else {
refreshToken();
}
}

public boolean isNull() {
return authToken == null;
}

public boolean hasExpired() {
Duration duration = Duration.between(Instant.now(), Instant.ofEpochMilli(authToken.expirationTimestampMillis()));
return duration.isNegative() || duration.getSeconds() < 30;
}

public String getAccessToken() {
return authToken.accessToken();
}

public void refreshToken() {
ResponseEntity<AuthToken> response = oauthRestClient.post()
.uri(props.getAccessTokenUri())
.body(formData)
.retrieve()
.toEntity(AuthToken.class);
if (response.getStatusCode().is2xxSuccessful()) {
authToken = response.getBody();
} else {
throw new IllegalStateException("Unable to refresh token");
}
}

private MultiValueMap<String, String> createFormData() {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();

formData.add("grant_type", "password");
formData.add("client_id", props.getClientId());
formData.add("client_secret", props.getClientSecret());
formData.add("username", props.getUsername());
formData.add("password", props.getPassword());
formData.add("scope", props.getScope());

return formData;
}

}
54 changes: 29 additions & 25 deletions src/main/java/no/fint/oauth/TokenService.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
package no.fint.oauth;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestClient;

@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fint.oauth.enabled", havingValue = "true")
public class TokenService {

private static final String BEARER_TOKEN_TEMPLATE = "Bearer %s";

@Autowired
@Qualifier("fintOauthRestTemplate")
private OAuth2RestTemplate restTemplate;

@Autowired
private OAuthTokenProps props;
private final RestClient oauthRestClient;
private final OAuthTokenProps props;
private final TokenInstance tokenInstance;

@PostConstruct
public void init() {
if (StringUtils.isEmpty(props.getRequestUrl())) {
if (ObjectUtils.isEmpty(props.getRequestUrl())) {
log.info("No request-url configured, will not initialize access token");
} else {
refreshToken(props.getRequestUrl());
refreshConnection(props.getRequestUrl());
}
}

private void refreshToken(String requestUrl) {
ResponseEntity<Void> response = restTemplate.getForEntity(requestUrl, Void.class);
private void refreshConnection(String requestUrl) {
ResponseEntity<Void> response = oauthRestClient.get()
.uri(requestUrl)
.headers(header -> header.add(
HttpHeaders.AUTHORIZATION, BEARER_TOKEN_TEMPLATE.formatted(tokenInstance.getAccessToken())
))
.retrieve()
.toBodilessEntity();
if (response.getStatusCode() != HttpStatus.OK) {
throw new IllegalStateException(String.format("Unable to get access token from %s. Status: %d", props.getRequestUrl(), response.getStatusCodeValue()));
throw new IllegalStateException(String.format("Unable to get access token from %s. Status: %d", props.getRequestUrl(), response.getStatusCode().value()));
}
}

public String getAccessToken(String requestUrl) {
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
if (accessToken.getExpiresIn() > 5) {
return accessToken.getValue();
} else {
refreshToken(requestUrl);
return restTemplate.getAccessToken().getValue();
if (tokenInstance.isNull() || tokenInstance.hasExpired()) {
tokenInstance.refreshToken();
refreshConnection(requestUrl);
}
return tokenInstance.getAccessToken();
}

public String getAccessToken() {
@@ -64,4 +67,5 @@ public String getBearerToken(String requestUrl) {
}
return null;
}

}
15 changes: 15 additions & 0 deletions src/main/java/no/fint/oauth/config/RestClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package no.fint.oauth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {

@Bean("oauthRestClient")
public RestClient oauthRestClient() {
return RestClient.builder().build();
}

}

This file was deleted.

Loading

0 comments on commit 6671b71

Please sign in to comment.