Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support api key auth #731

Merged
merged 2 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api-rest/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ db2rest:

auth:
enabled: ${ENABLE_AUTH:false}

provider: ${AUTH_PROVIDER:basic}
data:
source: ${AUTH_DATA_SOURCE}

Expand Down
43 changes: 32 additions & 11 deletions auth/auth-sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
}
10 changes: 10 additions & 0 deletions auth/auth-sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 15 additions & 12 deletions auth/src/main/java/com/homihq/db2rest/auth/AuthConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
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;
import com.homihq.db2rest.auth.data.ApiAuthDataProvider;
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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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
Expand All @@ -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))
Expand All @@ -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) {

Expand Down
25 changes: 7 additions & 18 deletions auth/src/main/java/com/homihq/db2rest/auth/AuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");

Expand All @@ -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);

Expand All @@ -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");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 org.apache.commons.lang3.StringUtils;
import org.springframework.util.AntPathMatcher;

@RequiredArgsConstructor
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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);

Expand Down Expand Up @@ -44,6 +47,9 @@ protected boolean isExcludedInternal(String requestUri, String method, List<Api
return true;
}

protected String getAuthHeader(HttpServletRequest request) {
return request.getHeader(AUTH_HEADER);
}

protected boolean authorizeInternal(UserDetail userDetail, String requestUri, String method,
List<ResourceRole> resourceRoleList, AntPathMatcher antPathMatcher) {
Expand Down
10 changes: 10 additions & 0 deletions auth/src/main/java/com/homihq/db2rest/auth/common/ApiKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.homihq.db2rest.auth.common;

import java.util.List;

public record ApiKey(
String key,
List<String> roles,
boolean active
) {
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -12,6 +9,8 @@ public interface AuthDataProvider {
List<User> getUsers();
List<ApiExcludedResource> getExcludedResources();

List<ApiKey> getApiKeys();

Optional<User> getUserByUsername(String username);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ public record AuthDataSource(
String name,
List<ResourceRole> resourceRoles,
List<ApiExcludedResource> excludedResources,
List<User> users
List<User> users,
List<ApiKey> apiKeys
) {
@Override
public List<ApiExcludedResource> excludedResources() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -34,6 +34,11 @@ public List<ResourceRole> getApiResourceRoles() {
return authDataSource.resourceRoles();
}

@Override
public List<ApiKey> getApiKeys() {
return List.of();
}

@Override
public List<User> getUsers() {
return null;
Expand Down
Loading