-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔐implement API key authorization with caching and circuit breaker in …
…gateway
- Loading branch information
Adnane Miliari
committed
Nov 29, 2024
1 parent
b6bd0fa
commit 9072765
Showing
43 changed files
with
896 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>dev.nano</groupId> | ||
<artifactId>demo-microservices</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>apiKey-manager</artifactId> | ||
|
||
<properties> | ||
<maven.compiler.source>17</maven.compiler.source> | ||
<maven.compiler.target>17</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-web</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-data-jpa</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-validation</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.postgresql</groupId> | ||
<artifactId>postgresql</artifactId> | ||
<scope>runtime</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>dev.nano</groupId> | ||
<artifactId>common</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-maven-plugin</artifactId> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>${maven.compiler.version}</version> | ||
<configuration> | ||
<source>${maven.compiler.source}</source> | ||
<target>${maven.compiler.target}</target> | ||
<annotationProcessorPaths> | ||
<path> | ||
<groupId>org.projectlombok</groupId> | ||
<artifactId>lombok</artifactId> | ||
<version>${lombok.version}</version> | ||
</path> | ||
</annotationProcessorPaths> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
11 changes: 11 additions & 0 deletions
11
apiKey-manager/src/main/java/dev/nano/ApiKeyManagerApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package dev.nano; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
||
@SpringBootApplication | ||
public class ApiKeyManagerApplication { | ||
public static void main(String[] args) { | ||
SpringApplication.run(ApiKeyManagerApplication.class, args); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
apiKey-manager/src/main/java/dev/nano/apikey/ApiKeyConstant.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package dev.nano.apikey; | ||
|
||
public class ApiKeyConstant { | ||
public static final String API_KEY_URI_REST_API = "/api/v1/apiKey-manager/api-keys"; | ||
public static final String API_KEY_NOT_FOUND = "Api key not found"; | ||
} |
56 changes: 56 additions & 0 deletions
56
apiKey-manager/src/main/java/dev/nano/apikey/ApiKeyEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package dev.nano.apikey; | ||
|
||
import dev.nano.application.ApplicationEntity; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
import lombok.experimental.SuperBuilder; | ||
|
||
import jakarta.persistence.*; | ||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
|
||
@Entity | ||
@Table(name = "api_keys") | ||
@Data @SuperBuilder | ||
@NoArgsConstructor @AllArgsConstructor | ||
public class ApiKeyEntity { | ||
@Id | ||
@SequenceGenerator( | ||
name = "customer_sequence", | ||
sequenceName = "customer_sequence", | ||
allocationSize = 1 | ||
) | ||
@GeneratedValue( | ||
strategy = GenerationType.SEQUENCE, | ||
generator = "customer_sequence" | ||
) | ||
@Column( | ||
name = "id", | ||
updatable = false | ||
) | ||
private Long id; | ||
|
||
@Column(unique = true, nullable = false) | ||
private String key; | ||
|
||
@Column(nullable = false, unique = true) | ||
private String client; | ||
|
||
private String description; | ||
|
||
private LocalDateTime createdDate; | ||
|
||
private LocalDateTime expirationDate; | ||
|
||
private boolean enabled; | ||
|
||
private boolean neverExpires; | ||
|
||
private boolean approved; | ||
|
||
private boolean revoked; | ||
|
||
@OneToMany(mappedBy = "apiKey") | ||
private List<ApplicationEntity> applications; | ||
} |
32 changes: 32 additions & 0 deletions
32
apiKey-manager/src/main/java/dev/nano/apikey/ApiKeyRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package dev.nano.apikey; | ||
|
||
import dev.nano.application.ApplicationName; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
|
||
import java.util.Optional; | ||
|
||
public interface ApiKeyRepository extends JpaRepository<ApiKeyEntity, Long> { | ||
@Query(""" | ||
SELECT ak FROM ApiKeyEntity ak | ||
INNER JOIN ApplicationEntity ap | ||
ON ak.id = ap.apiKey.id | ||
WHERE ak.key = :key | ||
AND ap.applicationName = :appName | ||
""") | ||
Optional<ApiKeyEntity> findByKeyAndApplicationName(String key, ApplicationName applicationName); | ||
|
||
@Query(""" | ||
SELECT | ||
CASE WHEN COUNT(ak) > 0 | ||
THEN TRUE | ||
ELSE FALSE | ||
END | ||
FROM ApiKeyEntity ak | ||
WHERE ak.key = :key | ||
""") | ||
boolean doesKeyExists(String key); | ||
|
||
@Query("SELECT ak FROM ApiKeyEntity ak WHERE ak.key = :key") | ||
Optional<ApiKeyEntity> findByKey(String key); | ||
} |
12 changes: 12 additions & 0 deletions
12
apiKey-manager/src/main/java/dev/nano/apikey/ApiKeyRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package dev.nano.apikey; | ||
|
||
import dev.nano.application.ApplicationName; | ||
|
||
import java.util.List; | ||
|
||
public record ApiKeyRequest( | ||
String client, | ||
String description, | ||
List<ApplicationName> applications | ||
) { | ||
} |
9 changes: 9 additions & 0 deletions
9
apiKey-manager/src/main/java/dev/nano/apikey/ApiKeyService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package dev.nano.apikey; | ||
|
||
import dev.nano.application.ApplicationName; | ||
|
||
public interface ApiKeyService { | ||
String save(ApiKeyRequest apiKeyRequest); | ||
void revokeApi(String key); | ||
boolean isAuthorized(String apiKey, ApplicationName applicationName); | ||
} |
104 changes: 104 additions & 0 deletions
104
apiKey-manager/src/main/java/dev/nano/apikey/ApiKeyServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package dev.nano.apikey; | ||
|
||
import dev.nano.application.ApplicationEntity; | ||
import dev.nano.application.ApplicationName; | ||
import dev.nano.application.ApplicationRepository; | ||
import exceptionhandler.core.ResourceNotFoundException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import static dev.nano.apikey.ApiKeyConstant.API_KEY_NOT_FOUND; | ||
|
||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ApiKeyServiceImpl implements ApiKeyService { | ||
private static final Integer EXPIRATION_DAYS = 30; | ||
private final ApiKeyRepository apiKeyRepository; | ||
private final ApplicationRepository applicationRepository; | ||
private final KeyGenerator keyGenerator; | ||
|
||
@Override | ||
public String save(ApiKeyRequest apiKeyRequest) { | ||
ApiKeyEntity apiKey = new ApiKeyEntity(); | ||
|
||
apiKey.setClient(apiKeyRequest.client()); | ||
apiKey.setDescription(apiKeyRequest.description()); | ||
|
||
String apiKeyValue = keyGenerator.generateKey(); | ||
apiKey.setKey(apiKeyValue); | ||
|
||
apiKey.setApproved(true); | ||
apiKey.setEnabled(true); | ||
apiKey.setNeverExpires(false); | ||
apiKey.setCreatedDate(LocalDateTime.now()); | ||
apiKey.setExpirationDate(LocalDateTime.now().plusDays(EXPIRATION_DAYS)); | ||
|
||
ApiKeyEntity savedApiKeyEntity = apiKeyRepository.save(apiKey); | ||
|
||
Set<ApplicationEntity> applications = Optional.ofNullable(apiKeyRequest.applications()) | ||
.orElse(List.of()) | ||
.stream().map(app -> ApplicationEntity.builder() | ||
.applicationName(app) | ||
.apiKey(savedApiKeyEntity) | ||
.revoked(false) | ||
.enabled(true) | ||
.build()) | ||
.collect(Collectors.toUnmodifiableSet()); | ||
|
||
applicationRepository.saveAll(applications); | ||
|
||
return apiKeyValue; | ||
} | ||
|
||
@Override | ||
public void revokeApi(String key) { | ||
ApiKeyEntity apiKey = apiKeyRepository.findByKey(key).orElseThrow( | ||
() -> new ResourceNotFoundException(API_KEY_NOT_FOUND)); | ||
|
||
apiKey.setRevoked(true); | ||
apiKey.setEnabled(false); | ||
apiKey.setApproved(false); | ||
apiKeyRepository.save(apiKey); | ||
|
||
// revoke all applications associated with this api key | ||
apiKey.getApplications().forEach(app -> { | ||
app.setRevoked(true); | ||
app.setEnabled(false); | ||
app.setApproved(false); | ||
applicationRepository.save(app); | ||
}); | ||
} | ||
|
||
@Override | ||
public boolean isAuthorized(String apiKey, ApplicationName applicationName) { | ||
Optional<ApiKeyEntity> optionalApiKey = apiKeyRepository.findByKeyAndApplicationName(apiKey, applicationName); | ||
|
||
if(optionalApiKey.isEmpty()) { | ||
return false; | ||
} | ||
|
||
ApiKeyEntity apiKeyEntity = optionalApiKey.get(); | ||
|
||
return apiKeyEntity.getApplications() | ||
.stream() | ||
.filter(app -> app.getApplicationName().equals(applicationName)) | ||
.findFirst() | ||
.map(app -> app.isEnabled() && | ||
app.isApproved() && | ||
!app.isRevoked() && | ||
apiKeyEntity.isEnabled() && | ||
apiKeyEntity.isApproved() && | ||
!apiKeyEntity.isRevoked() && | ||
(apiKeyEntity.isNeverExpires() || LocalDateTime.now().isBefore(apiKeyEntity.getExpirationDate())) // isAfter used to check if the expiration date is in the future | ||
) | ||
.orElse(false); | ||
|
||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
apiKey-manager/src/main/java/dev/nano/apikey/KeyGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package dev.nano.apikey; | ||
|
||
public interface KeyGenerator { | ||
String generateKey(); | ||
} |
13 changes: 13 additions & 0 deletions
13
apiKey-manager/src/main/java/dev/nano/apikey/UUIDKeyGeneratorImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package dev.nano.apikey; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.UUID; | ||
|
||
@Component | ||
public class UUIDKeyGeneratorImpl implements KeyGenerator { | ||
@Override | ||
public String generateKey() { | ||
return UUID.randomUUID().toString(); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
apiKey-manager/src/main/java/dev/nano/application/ApplicationConstant.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package dev.nano.application; | ||
|
||
public class ApplicationConstant { | ||
public static final String APPLICATION_URI_REST_API = "/api/v1/apiKey-manager/applications"; | ||
public static final String APPLICATION_NOT_FOUND = "Application not found"; | ||
} |
43 changes: 43 additions & 0 deletions
43
apiKey-manager/src/main/java/dev/nano/application/ApplicationEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package dev.nano.application; | ||
|
||
import dev.nano.apikey.ApiKeyEntity; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
import lombok.experimental.SuperBuilder; | ||
|
||
import jakarta.persistence.*; | ||
|
||
@Entity | ||
@Table(name = "applications") | ||
@Data @SuperBuilder | ||
@NoArgsConstructor @AllArgsConstructor | ||
public class ApplicationEntity { | ||
@Id | ||
@SequenceGenerator( | ||
name = "customer_sequence", | ||
sequenceName = "customer_sequence", | ||
allocationSize = 1 | ||
) | ||
@GeneratedValue( | ||
strategy = GenerationType.SEQUENCE, | ||
generator = "customer_sequence" | ||
) | ||
@Column( | ||
name = "id", | ||
updatable = false | ||
) | ||
private Integer id; | ||
@Enumerated(EnumType.STRING) | ||
@Column(nullable = false) | ||
private ApplicationName applicationName; | ||
private boolean enabled; | ||
private boolean approved; | ||
private boolean revoked; | ||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn( | ||
name = "api_key_id", | ||
referencedColumnName = "id" | ||
) | ||
private ApiKeyEntity apiKey; | ||
} |
10 changes: 10 additions & 0 deletions
10
apiKey-manager/src/main/java/dev/nano/application/ApplicationName.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package dev.nano.application; | ||
|
||
public enum ApplicationName { | ||
CUSTOMER, | ||
PRODUCT, | ||
ORDER, | ||
PAYMENT, | ||
NOTIFICATION, | ||
APIKEY_MANAGER | ||
} |
10 changes: 10 additions & 0 deletions
10
apiKey-manager/src/main/java/dev/nano/application/ApplicationRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package dev.nano.application; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface ApplicationRepository extends JpaRepository<ApplicationEntity, Integer> { | ||
Optional<ApplicationEntity> findByApplicationName(String applicationName); | ||
} | ||
|
Oops, something went wrong.