Skip to content

Commit

Permalink
ADD: dedicated consumer token class for authorizing api call for kode…
Browse files Browse the repository at this point in the history
…verk-api
  • Loading branch information
JeremiahUy committed Nov 29, 2024
1 parent dad3e78 commit 836b9de
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package no.nav.data.common.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class TokenExpiredException extends RuntimeException {

public TokenExpiredException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package no.nav.data.common.security.azure;

import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import no.nav.data.common.exceptions.TokenExpiredException;
import no.nav.data.common.security.dto.AccessTokenResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static java.time.LocalDateTime.now;
import static java.util.Objects.isNull;
import static org.springframework.http.MediaType.APPLICATION_JSON;

@Slf4j
@Component
@RequiredArgsConstructor
public class AzureTokenConsumer {

private final RestClient azureRestClient;

@Value("${AZURE_APP_CLIENT_ID}")
private String clientId;

@Value("${AZURE_APP_CLIENT_SECRET}")
private String clientSecret;

@Value("${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT}")
private String azureAdTokenEndpoint;

private Map<String, Token> tokens = new HashMap<>();

public String getToken(String scope) {
if (!tokens.containsKey(scope)) {
updateToken(scope);
}
updateTokenIfNeeded(scope);
return tokens.get(scope).getAccessToken();
}

private void updateTokenIfNeeded(String scope) {
synchronized (this) {
if (shouldRefresh(tokens.get(scope).getExpiry())) {
try {
updateToken(scope);
} catch (RuntimeException e) {
log.info("Feil fanget: {}", e.getMessage());
if (hasExpired(tokens.get(scope).getExpiry())) {
throw new TokenExpiredException("En feil oppsto ved forsøk på å refreshe AzureAD token", e);
}
}
}
}
}

private boolean hasExpired(LocalDateTime expiry) {
return isNull(expiry) || now().isAfter(expiry);
}

private boolean shouldRefresh(LocalDateTime expiry) {
return isNull(expiry) || now().plusMinutes(1).isAfter(expiry);
}

private void updateToken(String scope) {
var formParameters = formParameters(scope);

var response = azureRestClient.post()
.uri(azureAdTokenEndpoint)
.headers(httpHeaders -> {
httpHeaders.setAccept(Collections.singletonList(APPLICATION_JSON));
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
httpHeaders.setBasicAuth(clientId, clientSecret);
})
.body(formParameters)
.retrieve()
.body(AccessTokenResponse.class);

if (response != null) {
var token = Token.builder()
.accessToken(response.getAccessToken())
.expiry(now().plusSeconds(response.getExpiresIn()))
.build();
tokens.put(scope, token);
}
}

private MultiValueMap<String, String> formParameters(String scope) {
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add("grant_type", "client_credentials");
formParameters.add("scope", scope);

return formParameters;
}

@Getter
@Builder
private static class Token {

private String accessToken;
private LocalDateTime expiry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package no.nav.data.common.security.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class AccessTokenResponse {

@JsonProperty("expires_in")
private long expiresIn;

@JsonProperty("access_token")
private String accessToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import no.nav.data.common.security.azure.AzureTokenProvider;
import no.nav.data.common.security.azure.AzureTokenConsumer;
import no.nav.data.common.utils.MetricUtils;
import no.nav.data.polly.codelist.commoncode.dto.CommonCodeResponse;
import no.nav.data.polly.codelist.commoncode.nav.dto.CommonCode;
Expand Down Expand Up @@ -30,7 +30,7 @@ public class NavCommonCodeClient {

private final RestTemplate restTemplate;
private final NavCommonCodeProps props;
private final AzureTokenProvider azureTokenProvider;
private final AzureTokenConsumer azureTokenConsumer;

private final LoadingCache<String, List<CommonCodeResponse>> cache;

Expand All @@ -43,10 +43,10 @@ public class NavCommonCodeClient {
@Value("${polly.security.client.enabled}")
private boolean tokenedHeader;

public NavCommonCodeClient(RestTemplate restTemplate, NavCommonCodeProps props, AzureTokenProvider azureTokenProvider) {
public NavCommonCodeClient(RestTemplate restTemplate, NavCommonCodeProps props, AzureTokenConsumer azureTokenConsumer) {
this.restTemplate = restTemplate;
this.props = props;
this.azureTokenProvider = azureTokenProvider;
this.azureTokenConsumer = azureTokenConsumer;
this.cache = Caffeine.newBuilder().recordStats()
.expireAfterWrite(Duration.ofMinutes(10))
.maximumSize(100).build(this::getCodeList);
Expand All @@ -70,7 +70,7 @@ private List<CommonCodeResponse> getCodeList(String codeName) {
headers.set("Nav-Call-Id", randomUUID().toString());
headers.set("Nav-Consumer-Id", appName);
if(tokenedHeader) {
headers.setBearerAuth(azureTokenProvider.getApplicationTokenForResource(kodeverkScope));
headers.setBearerAuth(azureTokenConsumer.getToken(kodeverkScope));
}

var resp = restTemplate.exchange(props.getGetWithTextUrl(), HttpMethod.GET, new HttpEntity<>(headers), CommonCodeList.class, codeName, props.getLang());
Expand Down

0 comments on commit 836b9de

Please sign in to comment.