Skip to content

Commit

Permalink
Adding Azure AD security
Browse files Browse the repository at this point in the history
  • Loading branch information
alfespa17 committed Jun 8, 2021
1 parent b508256 commit 4fd8375
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 13 deletions.
2 changes: 1 addition & 1 deletion api-client-spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<gson.version>2.8.6</gson.version>
<feign.version>11.1</feign.version>
<okhttp.version>4.9.1</okhttp.version>
<revision>0.0.7</revision>
<revision>0.1.0</revision>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package org.azbuilder.api.spring.autoconfigure;

import feign.Feign;
import feign.Logger;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;
import org.azbuilder.api.client.RestClient;
import org.azbuilder.api.client.security.azure.ClientCredentialAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.slf4j.Slf4jLogger;
import feign.Logger;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;

Expand All @@ -23,13 +24,25 @@ public class RestClientAutoConfiguration {

@Bean
public RestClient restClient(RestClientProperties restClientProperties) {

ClientCredentialAuthentication clientCredentialAuthentication = new ClientCredentialAuthentication(
restClientProperties.getTenantId(),
restClientProperties.getClientId(),
restClientProperties.getClientSecret(),
restClientProperties.getScope()
);

okhttp3.OkHttpClient customHttpClient = new okhttp3.OkHttpClient.Builder()
.authenticator(clientCredentialAuthentication)
.addInterceptor(clientCredentialAuthentication)
.build();

RestClient restClient = Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.client( new OkHttpClient())
.client(new OkHttpClient(customHttpClient))
//.logger(new Slf4jLogger())
//.logLevel(Logger.Level.BASIC)

//.logLevel(Logger.Level.FULL)
.target(RestClient.class, restClientProperties.getUrl());
return restClient;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
@ConfigurationProperties(prefix = "org.azbuilder.api")
public class RestClientProperties {
private String url;
private String clientId;
private String clientSecret;
private String tenantId;
private String scope;
}
2 changes: 1 addition & 1 deletion api-client-spring-boot-starter-sample/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<revision>0.0.7</revision>
<revision>0.1.0</revision>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.azbuilder.api.client.RestClient;

@SpringBootTest
Expand All @@ -17,6 +15,7 @@ class ApiClientStarterSampleApplicationTests {
@Test
void contextLoads() {
assertNotNull(restClient);

}

}
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
org.azbuilder.api.url=http://localhost:8080
org.azbuilder.api.url=http://localhost:8080
#org.azbuilder.api.clientId=${AzureAdAppClientId}
#org.azbuilder.api.clientSecret=${AzureAdAppClientSecret}
#org.azbuilder.api.tenantId=${AzureAdAppTenantId}
#org.azbuilder.api.scope=${AzureAdAppScope}

#logging.level.root=INFO
2 changes: 1 addition & 1 deletion api-client-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>0.0.7</revision>
<revision>0.1.0</revision>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>

Expand Down
19 changes: 17 additions & 2 deletions api-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<revision>0.0.7</revision>
<revision>0.1.0</revision>
<feign.version>11.1</feign.version>
<feign-form.version>3.8.0</feign-form.version>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>

Expand All @@ -43,7 +44,21 @@
<artifactId>feign-core</artifactId>
<version>${feign.version}</version>
</dependency>

<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>${feign-form.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.azbuilder.api.client.security.azure;

import feign.Headers;
import feign.Param;
import feign.RequestLine;

import java.util.Map;

public interface ClientCredentialApi {

@RequestLine("GET /{tenantId}/oauth2/v2.0/token")
@Headers("Content-Type: application/x-www-form-urlencoded")
Map<String, String> getAccessToken(@Param("tenantId") String tenantId, @Param("grant_type") String grantType, @Param("client_id") String clientId, @Param("scope") String scope, @Param("client_secret") String clientSecret);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.azbuilder.api.client.security.azure;

import feign.Feign;
import feign.form.FormEncoder;
import feign.gson.GsonDecoder;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Map;

@Slf4j
public class ClientCredentialAuthentication implements Authenticator, Interceptor {

private static final String AZURE_ENDPOINT = "https://login.microsoftonline.com";
private static final String GRANT_TYPE = "client_credentials";

private String tenantId;
private String clientId;
private String clientSecret;
private String scope;
private ClientCredentialApi clientCredentialApi;
private String accessToken = "";

public ClientCredentialAuthentication(String tenantId, String clientId, String clientSecret, String scope) {
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
this.clientCredentialApi = Feign.builder()
.encoder(new FormEncoder())
.decoder(new GsonDecoder())
.target(ClientCredentialApi.class, AZURE_ENDPOINT);
}

@Override
public Request authenticate(Route route, Response response) {
log.info("Authentication error {}", response.code());
synchronized (this) {
this.accessToken = generateAccessToken();
}
return newRequestWithAccessToken(response.request(), this.accessToken);
}

@NonNull
private Request newRequestWithAccessToken(@NonNull Request request, String accessToken) {
return request.newBuilder()
.header("Authorization", "Bearer " + accessToken)
.build();
}

private String generateAccessToken() {
log.error("Renew Azure Active Directory Token");
String accessToken = "";
Map<String, String> response = clientCredentialApi.getAccessToken(
this.tenantId,
GRANT_TYPE,
this.clientId,
this.scope,
this.clientSecret);

if (response.containsKey("error")) {
throw new RuntimeException(response.get("error_description"));
}
accessToken = response.get("access_token");
log.info("Successful acquire new Azure AD access token");
return accessToken;
}


@Override
public Response intercept(Chain chain) throws IOException {
Request request = newRequestWithAccessToken(chain.request(), this.accessToken);
Response response = chain.proceed(request);

if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
synchronized (this) {
return chain.proceed(newRequestWithAccessToken(request, generateAccessToken()));
}
}

return response;
}
}

0 comments on commit 4fd8375

Please sign in to comment.