Skip to content

Commit

Permalink
Merge pull request #13 from quantics-io/release/0.4
Browse files Browse the repository at this point in the history
Release 0.4
  • Loading branch information
jomatt authored Nov 28, 2023
2 parents bb043d2 + 1366b7f commit d8cde15
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 47 deletions.
16 changes: 8 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath /> <!-- lookup parent from repository -->
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.quantics</groupId>
<artifactId>multitenant-oauth2-spring-boot-starter</artifactId>
<version>0.3.2</version>
<version>0.4.0</version>
<name>Spring Boot starter library for multi-tenant OAuth2 resource servers</name>

<licenses>
Expand All @@ -36,7 +36,7 @@
</scm>

<properties>
<java.version>11</java.version>
<java.version>17</java.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -83,7 +83,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<version>3.2.2</version>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
Expand All @@ -107,7 +107,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -120,7 +120,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<version>3.6.2</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand All @@ -133,7 +133,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.0.1</version>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.nimbusds.jwt.JWTParser;
import io.quantics.multitenant.tenantdetails.TenantDetails;
import io.quantics.multitenant.tenantdetails.TenantDetailsService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.oauth2.jwt.JwtDecoder;
Expand All @@ -12,7 +13,6 @@
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.nimbusds.jwt.proc.JWTClaimsSetAwareJWSKeySelector;
import com.nimbusds.jwt.proc.JWTProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
Expand All @@ -20,7 +20,7 @@
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* {@link EnableAutoConfiguration Auto-configuration} for multi-tenant resource server support.
* {@link AutoConfiguration Auto-configuration} for multi-tenant resource server support.
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore({
Expand All @@ -41,17 +41,20 @@ public class MultiTenantResourceServerAutoConfiguration {
AuthenticationManagerResolver.class
})
@Import(MultiTenantResourceServerJwtConfiguration.class)
static class JwtConfiguration { }
static class JwtConfiguration {
}

@Configuration
@ConditionalOnClass(SecurityFilterChain.class)
@Import(MultiTenantResourceServerWebSecurityConfiguration.class)
static class WebSecurityConfiguration { }
static class WebSecurityConfiguration {
}


@Configuration
@ConditionalOnClass({ HandlerInterceptor.class, WebMvcConfigurer.class })
@Import(MultiTenantResourceServerWebMvcConfiguration.class)
static class WebMvcConfiguration { }
static class WebMvcConfiguration {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.nimbusds.jwt.proc.JWTClaimsSetAwareJWSKeySelector;
import com.nimbusds.jwt.proc.JWTProcessor;
import io.quantics.multitenant.tenantdetails.TenantDetailsService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
Expand All @@ -19,8 +20,6 @@
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

import javax.servlet.http.HttpServletRequest;

/**
* Configures a {@link JwtDecoder} and exposes it as a bean.
* The {@link JwtDecoder} uses a {@link JWTProcessor} with a {@link MultiTenantJWSKeySelector} and a
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package io.quantics.multitenant.oauth2.config;

import jakarta.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;

/**
* Multi-tenant resource server properties.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import io.quantics.multitenant.TenantContext;
import io.quantics.multitenant.tenantdetails.TenantDetails;
import io.quantics.multitenant.tenantdetails.TenantDetailsService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
Expand All @@ -21,8 +22,6 @@
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
Expand All @@ -36,7 +35,6 @@
* </ul>
*/
@Configuration
@ConditionalOnMissingBean(WebMvcConfigurer.class)
public class MultiTenantResourceServerWebMvcConfiguration {

private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
Expand All @@ -54,8 +52,7 @@ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServl
if (tenantId != null) {
var tenant = tenantService.getById(tenantId);
if (tenant.isPresent()) {
logger.debug("Set TenantContext: " + tenant.get().getId());
TenantContext.setTenantId(tenant.get().getId());
setTenantContext(tenant.get());
return true;
}
}
Expand All @@ -67,10 +64,7 @@ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServl
@Override
public void postHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull Object handler, ModelAndView modelAndView) {
if (TenantContext.getTenantId() != null) {
logger.debug("Clear TenantContext: " + TenantContext.getTenantId());
TenantContext.clear();
}
clearTenantContext();
}

};
Expand All @@ -82,34 +76,41 @@ HandlerInterceptor multiTenantJwtInterceptor(TenantDetailsService tenantService)
return new HandlerInterceptor() {

@Override
@SuppressWarnings("rawtypes")
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull Object handler) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof AbstractOAuth2TokenAuthenticationToken) {
AbstractOAuth2TokenAuthenticationToken token = (AbstractOAuth2TokenAuthenticationToken) authentication;
if (authentication instanceof AbstractOAuth2TokenAuthenticationToken<?> token) {
String issuer = (String) token.getTokenAttributes().get("iss");
TenantDetails tenant = tenantService.getByIssuer(issuer)
var tenant = tenantService.getByIssuer(issuer)
.orElseThrow(() -> new IllegalArgumentException("Tenant not found for issuer: " + issuer));
logger.debug("Set TenantContext: " + tenant.getId());
TenantContext.setTenantId(tenant.getId());
setTenantContext(tenant);
}
return true;
}

@Override
public void postHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull Object handler, ModelAndView modelAndView) {
if (TenantContext.getTenantId() != null) {
logger.debug("Clear TenantContext: " + TenantContext.getTenantId());
TenantContext.clear();
}
clearTenantContext();
}
};
}

private static void setTenantContext(TenantDetails tenant) {
logger.debug("Set TenantContext: " + tenant.getId());
TenantContext.setTenantId(tenant.getId());
}

private static void clearTenantContext() {
if (TenantContext.getTenantId() != null) {
logger.debug("Clear TenantContext: " + TenantContext.getTenantId());
TenantContext.clear();
}
}

@Bean
@ConditionalOnBean(value = HandlerInterceptor.class, name = "multiTenantInterceptor")
@ConditionalOnMissingBean(WebMvcConfigurer.class)
WebMvcConfigurer multiTenantWebMvcConfigurer() {
return new WebMvcConfigurer() {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quantics.multitenant.oauth2.config;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
Expand All @@ -8,8 +9,6 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import javax.servlet.http.HttpServletRequest;

/**
* Configures a {@link SecurityFilterChain} when <i>jwt</i> is used as the mode for resolving the tenant.
* An {@link AuthenticationManagerResolver} takes care of performing the authentication using multiple
Expand All @@ -24,7 +23,7 @@ public class MultiTenantResourceServerWebSecurityConfiguration {
public SecurityFilterChain multiTenantHeaderFilterChain(HttpSecurity http) throws Exception {

http.authorizeHttpRequests(authz -> authz
.anyRequest().permitAll()
.anyRequest().authenticated()
);

return http.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.quantics.multitenant.oauth2.config.MultiTenantResourceServerAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
Expand All @@ -38,6 +39,7 @@
"spring.security.oauth2.resourceserver.multitenant.header.header-name="
+ MultiTenantHeaderApplicationTests.HEADER_NAME,
})
@Import(MultiTenantHeaderTestConfiguration.class)
@AutoConfigureMockMvc
class MultiTenantHeaderApplicationTests {

Expand Down Expand Up @@ -81,6 +83,18 @@ void getWithKnownTenant_shouldReturnHelloWorld() throws Exception {

@Test
void getWithUnknownTenant_shouldReturnUnauthorized() throws Exception {
String tenantId = "test-tenant";

Mockito.doReturn(Optional.empty())
.when(tenantService).getById(tenantId);

mockMvc.perform(get("/").header(HEADER_NAME, tenantId))
.andDo(print())
.andExpect(status().isUnauthorized());
}

@Test
void getWithoutTenant_shouldReturnUnauthorized() throws Exception {
mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().isUnauthorized());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quantics.multitenant.oauth2.config;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@TestConfiguration
class MultiTenantHeaderTestConfiguration {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http.authorizeHttpRequests(authz -> authz
.requestMatchers("/").permitAll()
);

return http.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import com.nimbusds.jwt.proc.JWTClaimsSetAwareJWSKeySelector;
import com.nimbusds.jwt.proc.JWTProcessor;
import io.quantics.multitenant.app.TestApplication;
import io.quantics.multitenant.tenantdetails.TenantDetailsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
Expand All @@ -25,15 +24,13 @@
"spring.security.oauth2.resourceserver.multitenant.jwt.authorities-converter="
+ "io.quantics.multitenant.oauth2.config.KeycloakRealmAuthoritiesConverter",
})
@Import(MultiTenantJwtTestConfiguration.class)
@AutoConfigureMockMvc
class MultiTenantJwtAuthenticationConverterApplicationTests {

@Autowired
private ApplicationContext context;

@MockBean
private TenantDetailsService tenantService;

@Test
void contextLoads() {
assertThat(context.getBean(JWTClaimsSetAwareJWSKeySelector.class)).isNotNull();
Expand Down
Loading

0 comments on commit d8cde15

Please sign in to comment.