From 12a510c29b177ff55cb4c9a16ade2649072d5e1b Mon Sep 17 00:00:00 2001 From: Lenur Serveriiev Date: Wed, 9 Oct 2024 18:12:00 +0200 Subject: [PATCH 1/2] Support api key auth --- api-rest/src/main/resources/application.yml | 2 +- auth/auth-sample.json | 43 +++++++++++----- auth/auth-sample.yml | 10 ++++ .../db2rest/auth/AuthConfiguration.java | 27 +++++----- .../com/homihq/db2rest/auth/AuthFilter.java | 25 +++------- .../auth/apikey/ApiKeyAuthProvider.java | 49 +++++++++++++++++++ .../db2rest/auth/basic/BasicAuthProvider.java | 19 +++++-- .../auth/common/AbstractAuthProvider.java | 10 +++- .../homihq/db2rest/auth/common/ApiKey.java | 10 ++++ .../db2rest/auth/common/AuthDataProvider.java | 5 +- .../db2rest/auth/common/AuthDataSource.java | 3 +- .../auth/data/ApiAuthDataProvider.java | 7 ++- .../auth/data/FileAuthDataProvider.java | 6 ++- .../db2rest/auth/data/NoAuthdataProvider.java | 6 +++ .../db2rest/auth/jwt/JwtAuthProvider.java | 20 +++++--- 15 files changed, 178 insertions(+), 64 deletions(-) create mode 100644 auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java create mode 100644 auth/src/main/java/com/homihq/db2rest/auth/common/ApiKey.java diff --git a/api-rest/src/main/resources/application.yml b/api-rest/src/main/resources/application.yml index 3f83f2fe..8ca49a5a 100644 --- a/api-rest/src/main/resources/application.yml +++ b/api-rest/src/main/resources/application.yml @@ -34,7 +34,7 @@ db2rest: auth: enabled: ${ENABLE_AUTH:false} - + provider: ${AUTH_PROVIDER:basic} data: source: ${AUTH_DATA_SOURCE} diff --git a/auth/auth-sample.json b/auth/auth-sample.json index fdd1cb81..1e1ae2e7 100644 --- a/auth/auth-sample.json +++ b/auth/auth-sample.json @@ -58,29 +58,50 @@ "method": "*" } ], - "account": [ + "users": [ { - "appId": "admin", - "credential": "0192023A7BBD73250516F069DF18B500", - "salt": 123, - "role": [ + "username": "admin", + "password": "0192023A7BBD73250516F069DF18B500", + "roles": [ "role1", "role2" ] }, { - "appId": "root", - "credential": 23456, - "role": [ + "username": "root", + "password": 23456, + "roles": [ "role1" ] }, { - "appId": "tom", - "credential": 32113, - "role": [ + "username": "tom", + "password": 32113, + "roles": [ "role3" ] } + ], + "apiKeys": [ + { + "key": "apikey1", + "roles": [ + "admin" + ], + "active": true + }, + { + "key": "apikey2", + "roles": [ + "admin" + ] + }, + { + "key": "apiKey3", + "roles": [ + "user" + ], + "active": true + } ] } diff --git a/auth/auth-sample.yml b/auth/auth-sample.yml index 133e8334..b2fbb2d8 100644 --- a/auth/auth-sample.yml +++ b/auth/auth-sample.yml @@ -35,3 +35,13 @@ users: - username: tom password: 32113 roles: [role3] + +apiKeys: + - key: apikey1 + roles: [admin] + active: true + - key: apikey2 + roles: [admin] + - key: apiKey3 + roles: [user] + active: true diff --git a/auth/src/main/java/com/homihq/db2rest/auth/AuthConfiguration.java b/auth/src/main/java/com/homihq/db2rest/auth/AuthConfiguration.java index 3ecf024a..e72605c8 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/AuthConfiguration.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/AuthConfiguration.java @@ -1,8 +1,7 @@ package com.homihq.db2rest.auth; -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; import com.fasterxml.jackson.databind.ObjectMapper; +import com.homihq.db2rest.auth.apikey.ApiKeyAuthProvider; import com.homihq.db2rest.auth.basic.BasicAuthProvider; import com.homihq.db2rest.auth.common.AbstractAuthProvider; import com.homihq.db2rest.auth.common.AuthDataProvider; @@ -10,8 +9,6 @@ import com.homihq.db2rest.auth.data.AuthDataProperties; import com.homihq.db2rest.auth.data.FileAuthDataProvider; import com.homihq.db2rest.auth.data.NoAuthdataProvider; -import com.homihq.db2rest.auth.jwt.AlgorithmFactory; -import com.homihq.db2rest.auth.jwt.JwtAuthProvider; import com.homihq.db2rest.auth.jwt.JwtProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,9 +16,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.AntPathMatcher; -import org.springframework.web.client.RestClient; - -import java.util.List; @Slf4j @Configuration @@ -38,16 +32,19 @@ public AntPathMatcher authAntPathMatcher() { } @Bean - public AuthFilter authFilter(AuthDataProperties authDataProperties, ObjectMapper objectMapper) throws Exception { + public AuthFilter authFilter( + AbstractAuthProvider authProvider, + ObjectMapper objectMapper + ) { log.info("** Auth enabled. Initializing auth components."); - return new AuthFilter(authProvider(authDataProperties), objectMapper); + return new AuthFilter(authProvider, objectMapper); } @Bean - public AbstractAuthProvider authProvider(AuthDataProperties authDataProperties) throws Exception{ - return new BasicAuthProvider(authDataProvider(authDataProperties),authAntPathMatcher()); - + @ConditionalOnProperty(prefix = "db2rest.auth", name = "provider", havingValue = "apiKey") + public AbstractAuthProvider apiKeyAuthProvider(AuthDataProperties authDataProperties) { + return new ApiKeyAuthProvider(authDataProvider(authDataProperties), authAntPathMatcher()); /* JWTVerifier jwtVerifier = JWT.require(AlgorithmFactory.getAlgorithm(jwtProperties)) @@ -61,6 +58,12 @@ public AbstractAuthProvider authProvider(AuthDataProperties authDataProperties) */ } + @Bean + @ConditionalOnProperty(prefix = "db2rest.auth", name = "provider", havingValue = "basic") + public AbstractAuthProvider basicAuthProvider(AuthDataProperties authDataProperties) { + return new BasicAuthProvider(authDataProvider(authDataProperties), authAntPathMatcher()); + } + @Bean public AuthDataProvider authDataProvider(AuthDataProperties authDataProperties) { diff --git a/auth/src/main/java/com/homihq/db2rest/auth/AuthFilter.java b/auth/src/main/java/com/homihq/db2rest/auth/AuthFilter.java index 228e0675..24ead10e 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/AuthFilter.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/AuthFilter.java @@ -9,7 +9,6 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.UrlPathHelper; @@ -25,13 +24,14 @@ public class AuthFilter extends OncePerRequestFilter { private final AbstractAuthProvider authProvider; private final ObjectMapper objectMapper; - String AUTH_HEADER = "Authorization"; - UrlPathHelper urlPathHelper = new UrlPathHelper(); + private final UrlPathHelper urlPathHelper = new UrlPathHelper(); @Override - protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, - final FilterChain filterChain) throws ServletException, IOException { - + protected void doFilterInternal( + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain + ) throws ServletException, IOException { log.info("Handling Auth"); @@ -41,18 +41,9 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ log.info("Request URI - {}", requestUri); if(!authProvider.isExcluded(requestUri, method)) { - String authHeaderValue = request.getHeader(AUTH_HEADER); - - if(StringUtils.isBlank(authHeaderValue)) { - String errorMessage = "Auth token not provided in header. Please add header " - + AUTH_HEADER + " with valid Auth token."; - addError(errorMessage, request, response); - return; - } //authenticate - UserDetail userDetail = - authProvider.authenticate(authHeaderValue); + UserDetail userDetail = authProvider.authenticate(request); log.info("user detail - {}", userDetail); @@ -76,8 +67,6 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ log.info("URI in whitelist. Security checks not applied."); } - - filterChain.doFilter(request, response); logger.info("Completed Auth Filter"); diff --git a/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java new file mode 100644 index 00000000..cc01bbdd --- /dev/null +++ b/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java @@ -0,0 +1,49 @@ +package com.homihq.db2rest.auth.apikey; + +import com.homihq.db2rest.auth.common.AbstractAuthProvider; +import com.homihq.db2rest.auth.common.ApiKey; +import com.homihq.db2rest.auth.common.AuthDataProvider; +import com.homihq.db2rest.auth.common.UserDetail; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.AntPathMatcher; + +@RequiredArgsConstructor +@Slf4j +public class ApiKeyAuthProvider extends AbstractAuthProvider { + + private static final String API_KEY_HEADER = "X-API-KEY"; + + private final AuthDataProvider authDataProvider; + private final AntPathMatcher antPathMatcher; + + @Override + public boolean canHandle(HttpServletRequest request) { + String apiKey = request.getHeader(API_KEY_HEADER); + return StringUtils.isNotBlank(apiKey); + } + + @Override + public UserDetail authenticate(HttpServletRequest request) { + String apiKey = request.getHeader(API_KEY_HEADER); + return authDataProvider.getApiKeys() + .stream() + .filter(a -> a.key().equals(apiKey)) + .filter(ApiKey::active) + .map(a -> new UserDetail(a.key(), a.roles())) + .findFirst() + .orElse(null); + } + + @Override + public boolean authorize(UserDetail userDetail, String requestUri, String method) { + return this.authorizeInternal(userDetail, requestUri, method, authDataProvider.getApiResourceRoles(), antPathMatcher); + } + + @Override + public boolean isExcluded(String requestUri, String method) { + return super.isExcludedInternal(requestUri, method, authDataProvider.getExcludedResources(), antPathMatcher); + } +} diff --git a/auth/src/main/java/com/homihq/db2rest/auth/basic/BasicAuthProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/basic/BasicAuthProvider.java index 57390f7f..f2060816 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/basic/BasicAuthProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/basic/BasicAuthProvider.java @@ -1,6 +1,10 @@ package com.homihq.db2rest.auth.basic; -import com.homihq.db2rest.auth.common.*; +import com.homihq.db2rest.auth.common.AbstractAuthProvider; +import com.homihq.db2rest.auth.common.AuthDataProvider; +import com.homihq.db2rest.auth.common.User; +import com.homihq.db2rest.auth.common.UserDetail; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -15,16 +19,21 @@ @Slf4j public class BasicAuthProvider extends AbstractAuthProvider { + private static final String BASIC_AUTH = "Basic"; + private final AuthDataProvider authDataProvider; private final AntPathMatcher antPathMatcher; + @Override - public boolean canHandle(String authHeader) { - return StringUtils.isNotBlank(authHeader) && authHeader.startsWith("Basic "); + public boolean canHandle(HttpServletRequest request) { + String authHeader = this.getAuthHeader(request); + return StringUtils.isNotBlank(authHeader) && authHeader.startsWith(BASIC_AUTH); } @Override - public UserDetail authenticate(String authHeader) { - String base64Credentials = authHeader.substring("Basic ".length()); + public UserDetail authenticate(HttpServletRequest request) { + String authHeader = this.getAuthHeader(request); + String base64Credentials = authHeader.substring(String.format("%s ", BASIC_AUTH).length()); byte[] decodedCredentials = Base64.getDecoder().decode(base64Credentials); String credentials = new String(decodedCredentials, StandardCharsets.UTF_8); diff --git a/auth/src/main/java/com/homihq/db2rest/auth/common/AbstractAuthProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/common/AbstractAuthProvider.java index b375b3de..f1da0685 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/common/AbstractAuthProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/common/AbstractAuthProvider.java @@ -1,5 +1,6 @@ package com.homihq.db2rest.auth.common; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.util.AntPathMatcher; @@ -11,11 +12,13 @@ @Slf4j public abstract class AbstractAuthProvider{ + private static final String AUTH_HEADER = "Authorization"; + private final String [] DEFAULT_WHITELIST = {"/actuator/**"} ; - public abstract boolean canHandle(String authHeader); + public abstract boolean canHandle(HttpServletRequest request); - public abstract UserDetail authenticate(String authHeader); + public abstract UserDetail authenticate(HttpServletRequest request); public abstract boolean authorize(UserDetail userDetail, String resourceUri, String method); @@ -44,6 +47,9 @@ protected boolean isExcludedInternal(String requestUri, String method, List resourceRoleList, AntPathMatcher antPathMatcher) { diff --git a/auth/src/main/java/com/homihq/db2rest/auth/common/ApiKey.java b/auth/src/main/java/com/homihq/db2rest/auth/common/ApiKey.java new file mode 100644 index 00000000..b216b2b3 --- /dev/null +++ b/auth/src/main/java/com/homihq/db2rest/auth/common/ApiKey.java @@ -0,0 +1,10 @@ +package com.homihq.db2rest.auth.common; + +import java.util.List; + +public record ApiKey( + String key, + List roles, + boolean active +) { +} diff --git a/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataProvider.java index f78014eb..7602d476 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataProvider.java @@ -1,8 +1,5 @@ package com.homihq.db2rest.auth.common; -import com.homihq.db2rest.auth.exception.AuthException; -import org.apache.commons.lang3.StringUtils; - import java.util.List; import java.util.Optional; @@ -12,6 +9,8 @@ public interface AuthDataProvider { List getUsers(); List getExcludedResources(); + List getApiKeys(); + Optional getUserByUsername(String username); diff --git a/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataSource.java b/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataSource.java index fb94adc7..a9be8679 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataSource.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/common/AuthDataSource.java @@ -6,7 +6,8 @@ public record AuthDataSource( String name, List resourceRoles, List excludedResources, - List users + List users, + List apiKeys ) { @Override public List excludedResources() { diff --git a/auth/src/main/java/com/homihq/db2rest/auth/data/ApiAuthDataProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/data/ApiAuthDataProvider.java index fd996395..acb318c9 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/data/ApiAuthDataProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/data/ApiAuthDataProvider.java @@ -12,7 +12,7 @@ @Slf4j public class ApiAuthDataProvider implements AuthDataProvider { - private AuthDataSource authDataSource; + private final AuthDataSource authDataSource; public ApiAuthDataProvider(String apiEndPoint, String apiKey) { RestClient restClient = RestClient.builder() @@ -34,6 +34,11 @@ public List getApiResourceRoles() { return authDataSource.resourceRoles(); } + @Override + public List getApiKeys() { + return List.of(); + } + @Override public List getUsers() { return null; diff --git a/auth/src/main/java/com/homihq/db2rest/auth/data/FileAuthDataProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/data/FileAuthDataProvider.java index 310c24b7..0e501b3a 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/data/FileAuthDataProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/data/FileAuthDataProvider.java @@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; - import java.io.FileInputStream; import java.io.InputStream; import java.util.List; @@ -38,7 +37,10 @@ public List getApiResourceRoles() { return authDataSource.resourceRoles(); } - + @Override + public List getApiKeys() { + return this.authDataSource.apiKeys(); + } @Override public List getUsers() { diff --git a/auth/src/main/java/com/homihq/db2rest/auth/data/NoAuthdataProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/data/NoAuthdataProvider.java index f5508d2f..387441d9 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/data/NoAuthdataProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/data/NoAuthdataProvider.java @@ -2,6 +2,7 @@ import com.homihq.db2rest.auth.common.ApiExcludedResource; +import com.homihq.db2rest.auth.common.ApiKey; import com.homihq.db2rest.auth.common.AuthDataProvider; import com.homihq.db2rest.auth.common.ResourceRole; import com.homihq.db2rest.auth.common.User; @@ -20,6 +21,11 @@ public List getExcludedResources() { return List.of(); } + @Override + public List getApiKeys() { + return List.of(); + } + @Override public Optional getUserByUsername(String username) { return Optional.empty(); diff --git a/auth/src/main/java/com/homihq/db2rest/auth/jwt/JwtAuthProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/jwt/JwtAuthProvider.java index 6d24b4e2..803a24fd 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/jwt/JwtAuthProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/jwt/JwtAuthProvider.java @@ -3,33 +3,37 @@ import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import com.homihq.db2rest.auth.common.*; -import com.homihq.db2rest.auth.exception.AuthException; +import com.homihq.db2rest.auth.common.AbstractAuthProvider; +import com.homihq.db2rest.auth.common.AuthDataProvider; +import com.homihq.db2rest.auth.common.UserDetail; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.util.AntPathMatcher; -import javax.swing.text.html.Option; import java.util.List; -import java.util.Optional; @Slf4j @RequiredArgsConstructor public class JwtAuthProvider extends AbstractAuthProvider { + private static final String BEARER_AUTH = "Bearer"; + private final JWTVerifier jwtVerifier; private final AuthDataProvider authDataProvider; private final AntPathMatcher antPathMatcher; @Override - public boolean canHandle(String authHeader) { - return StringUtils.isNotBlank(authHeader) && authHeader.startsWith("Bearer "); + public boolean canHandle(HttpServletRequest request) { + String authHeader = this.getAuthHeader(request); + return StringUtils.isNotBlank(authHeader) && authHeader.startsWith(BEARER_AUTH); } @Override - public UserDetail authenticate(String authHeader) { - String token = StringUtils.replace(authHeader, "Bearer ", "", 1); + public UserDetail authenticate(HttpServletRequest request) { + String authHeader = this.getAuthHeader(request); + String token = StringUtils.replace(authHeader, String.format("%s ", BEARER_AUTH), "", 1); try { DecodedJWT decodedJWT = jwtVerifier.verify(token); From 28c7193922dcf0c283b65afc09d6a657b1568f83 Mon Sep 17 00:00:00 2001 From: Lenur Serveriiev Date: Wed, 9 Oct 2024 18:14:42 +0200 Subject: [PATCH 2/2] Removed unused annotation --- .../java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java b/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java index cc01bbdd..9fc7073b 100644 --- a/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java +++ b/auth/src/main/java/com/homihq/db2rest/auth/apikey/ApiKeyAuthProvider.java @@ -6,12 +6,10 @@ import com.homihq.db2rest.auth.common.UserDetail; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.util.AntPathMatcher; @RequiredArgsConstructor -@Slf4j public class ApiKeyAuthProvider extends AbstractAuthProvider { private static final String API_KEY_HEADER = "X-API-KEY";