Skip to content

Commit

Permalink
Backend initial app with authorization server configuration and OpenA…
Browse files Browse the repository at this point in the history
…PI generator (#243)

* H2 DB or Postgresql TestContainer available for local development

* added springdoc

* logging URL in log message

* Minimal Members service for Authorization server

* Auth server keys generator + example key

* Authorization server with google login

github doesn't work well because there is not OIDC (only OAuth2)

* API server security configuration

* added springdoc configuration

* Spring security chains configuration

- API (application/json) is secured using KLabis OAuth2 JWT token
- login form has social login using Google
- springdoc is not secured
- authorization server

* swagger-ui from app shows static openapi yaml from project root folder

* OpenAPI updates (cleanup)

* OpenAPI generator for server

* Fixed in API specs

* Added backend compiled files to ignore list
  • Loading branch information
dapolach authored Jun 4, 2024
1 parent 8aff820 commit a08044a
Show file tree
Hide file tree
Showing 29 changed files with 1,238 additions and 32 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

# jetbrains
.idea/
**/*.iml

# vscode
.vscode/

# backend excludes
backend/target/**
15 changes: 15 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Klabis backend

## Spusteni na localhost
Aplikace ma nakonfigurovane prostredi pomoci [TestContainers](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testcontainers.at-development-time). Pro spusteni je potreba mit nainstalovany docker. Spusteni je pak mozne pomoci prikazu
```shell
mvn spring-boot:test-run
```
Alternativne lze aplikaci spolecne s TestContainers spustit pomoci java main tridy `club.klabis.KlabisAppWithTestContainers`

# DevOps

## Autorizacni server

### Vygenerovani noveho klice pro JWT tokeny
`club.klabis.config.authserver.generatejwtkeys.JKWKeyGenerator`
20 changes: 20 additions & 0 deletions backend/authorization_server.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
### OIDC metadata
GET {{host}}/.well-known/openid-configuration

### OIDC UserInfo
GET {{host}}/oidc/userinfo
Authorization: Bearer {{$auth.token("test")}}

### OAuth2 token introspection - ID token
POST {{host}}/oauth2/introspect
Authorization: Basic test test
Content-Type: application/x-www-form-urlencoded

token={{$auth.idToken("test")}}

### OAuth2 token introspection - access token
POST {{host}}/oauth2/introspect
Authorization: Basic test test
Content-Type: application/x-www-form-urlencoded

token={{$auth.token("test")}}
159 changes: 156 additions & 3 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,94 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
<version>3.2.0</version>
</parent>

<properties>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.release>21</maven.compiler.release>
<project.docker.image.name>registry.polach.cloud/zbm/web-2.0/${project.artifactId}:${project.version}</project.docker.image.name>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</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-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<!-- for openapi generator -->
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.6</version>
</dependency>
</dependencies>

<build>
Expand All @@ -32,15 +108,92 @@
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>registry.polach.cloud/zbm/web-2.0/${project.artifactId}:${project.version}</name>
<name>${project.docker.image.name}</name>
</image>
</configuration>
<executions>
<execution>
<id>springbootbuildimage</id>
<goals>
<goal>build-image</goal>
</goals>
</execution>
<execution>
<id>springbootbuildinfo</id>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<encoding.source>UTF-8</encoding.source>
<encoding.reporting>UTF-8</encoding.reporting>
<java.version>${java.version}</java.version>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version> <!-- or the latest version -->
<executions>
<execution>
<id>copy-resources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/classes/static</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/..</directory> <!-- your source directory -->
<includes>
<include>klabis-api-spec.yaml</include> <!-- the specific file you want to copy -->
</includes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<!-- RELEASE_VERSION -->
<version>7.6.0</version>
<!-- /RELEASE_VERSION -->
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/../klabis-api-spec.yaml</inputSpec>
<generatorName>spring</generatorName>
<library>spring-boot</library>
<modelPackage>club.klabis.api.dto</modelPackage>
<modelNameSuffix>Dto</modelNameSuffix>
<apiPackage>club.klabis.api</apiPackage>
<configOptions>
<sourceFolder>src/gen/java/main</sourceFolder>
<dateLibrary>java8</dateLibrary>
<!--library>spring-boot</library-->
<useSpringBoot3>true</useSpringBoot3>
<useOptional>true</useOptional>
<openApiNullable>false</openApiNullable>
<generateBuilders>true</generateBuilders>
<useSpringController>true</useSpringController>
<hateoas>false</hateoas>
<booleanGetterPrefix>is</booleanGetterPrefix>
<interfaceOnly>true</interfaceOnly>
<defaultInterfaces>false</defaultInterfaces>
<useBeanValidation>true</useBeanValidation>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package club.klabis.adapters.api;

import club.klabis.config.authserver.AuthorizationServerConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class ApiSecurityConfiguration {

public static RequestMatcher API_ENDPOINTS_MATCHER = new MediaTypeRequestMatcher(MediaType.APPLICATION_JSON);

@Bean
@Order(AuthorizationServerConfiguration.AFTER_AUTH_SERVER_SECURITY_ORDER)
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher(API_ENDPOINTS_MATCHER)
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
.build();
}

}
18 changes: 18 additions & 0 deletions backend/src/main/java/club/klabis/adapters/api/TestController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package club.klabis.adapters.api;

import net.minidev.json.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class TestController {

@GetMapping(value = "/api/test", produces = MediaType.APPLICATION_JSON_VALUE)
public JSONObject getResponse(Authentication principal) {
return new JSONObject(Map.of("message", "Hello %s!".formatted(principal.getName()))); }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package club.klabis.config.authserver;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

@Configuration
public class AuthorizationServerConfiguration {

public static final int AUTH_SERVER_SECURITY_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
public static final int apAUTH_SERVER_LOGIN_PAGE = Ordered.LOWEST_PRECEDENCE - 5;
public static final int BEFORE_AUTH_SERVER_SECURITY_ORDER = AUTH_SERVER_SECURITY_ORDER - 2;
public static final int AFTER_AUTH_SERVER_SECURITY_ORDER = AUTH_SERVER_SECURITY_ORDER + 2;

@Bean
@Order(AUTH_SERVER_SECURITY_ORDER)
public SecurityFilterChain authorizationSecurityFilterChain(
HttpSecurity http,
DaoAuthenticationProvider daoAuthenticationProvider
) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.authenticationProvider(daoAuthenticationProvider)
)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0

// OAuth2 resource server to authenticate OIDC userInfo and/or client registration endpoints
http.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));

http.exceptionHandling(
exceptions ->
exceptions.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login")
)
);

return http.build();
}

@Bean
public OAuth2AuthorizationService authorizationService() {
// TODO: replace with DB
return new InMemoryOAuth2AuthorizationService();
}

@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(
PasswordEncoder passwordEncoder, UserDetailsService userDetailsService
) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
return daoAuthenticationProvider;
}
}
Loading

0 comments on commit a08044a

Please sign in to comment.