Upgrade to Spring Boot 3

Signed-off-by: William Brawner <me@wbrawner.com>
This commit is contained in:
William Brawner 2023-03-04 09:03:36 -07:00
parent 41466a1134
commit 6f68065b95
27 changed files with 125 additions and 118 deletions

View file

@ -3,29 +3,21 @@ apply plugin: 'application'
apply plugin: "io.spring.dependency-management"
apply plugin: "org.springframework.boot"
repositories {
mavenLocal()
mavenCentral()
maven {
url = "http://repo.maven.apache.org/maven2"
}
}
dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.session:spring-session-jdbc"
implementation "org.springframework.boot:spring-boot-starter-web"
runtimeOnly "mysql:mysql-connector-java:8.0.15"
runtimeOnly "org.postgresql:postgresql:42.2.23"
testImplementation "org.springframework.boot:spring-boot-starter-test"
testImplementation "org.springframework.security:spring-security-test:5.1.5.RELEASE"
}
jar {
description = "twigs-server"
description = "twigs"
}
mainClassName = "com.wbrawner.twigs.TwigsServerApplication"
sourceCompatibility = 14
targetCompatibility = 14
sourceCompatibility = 17
targetCompatibility = 17

View file

@ -2,8 +2,10 @@ package com.wbrawner.twigs.budget;
import com.wbrawner.twigs.category.Category;
import com.wbrawner.twigs.transaction.Transaction;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

View file

@ -6,6 +6,7 @@ import com.wbrawner.twigs.permission.UserPermissionRepository;
import com.wbrawner.twigs.transaction.TransactionRepository;
import com.wbrawner.twigs.user.User;
import com.wbrawner.twigs.user.UserRepository;
import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
@ -14,7 +15,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

View file

@ -1,6 +1,8 @@
package com.wbrawner.twigs.budget;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface BudgetRepository extends PagingAndSortingRepository<Budget, String> {
public interface BudgetRepository extends CrudRepository<Budget, String>,
PagingAndSortingRepository<Budget, String> {
}

View file

@ -2,7 +2,6 @@ package com.wbrawner.twigs.budget;
import com.wbrawner.twigs.permission.UserPermissionRequest;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@ -23,7 +22,6 @@ public class BudgetRequest {
this.users.addAll(users);
}
@NotNull
public Set<UserPermissionRequest> getUsers() {
return Set.copyOf(users);
}

View file

@ -1,8 +1,7 @@
package com.wbrawner.twigs.category;
import com.wbrawner.twigs.budget.Budget;
import javax.persistence.*;
import jakarta.persistence.*;
import static com.wbrawner.twigs.Utils.randomId;

View file

@ -5,6 +5,7 @@ import com.wbrawner.twigs.permission.Permission;
import com.wbrawner.twigs.permission.UserPermission;
import com.wbrawner.twigs.permission.UserPermissionRepository;
import com.wbrawner.twigs.transaction.TransactionRepository;
import jakarta.transaction.Transactional;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
@ -12,7 +13,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.util.List;
import java.util.stream.Collectors;

View file

@ -3,12 +3,14 @@ package com.wbrawner.twigs.category;
import com.wbrawner.twigs.budget.Budget;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
import java.util.Optional;
public interface CategoryRepository extends PagingAndSortingRepository<Category, String> {
public interface CategoryRepository extends CrudRepository<Category, String>,
PagingAndSortingRepository<Category, String> {
List<Category> findAllByBudget(Budget budget, Pageable pageable);
@Query("SELECT c FROM Category c where c.budget IN (:budgets) AND (:expense IS NULL OR c.expense = :expense) AND (:archived IS NULL OR c.archived = :archived)")

View file

@ -1,10 +0,0 @@
package com.wbrawner.twigs.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurity extends GlobalMethodSecurityConfiguration {
}

View file

@ -7,15 +7,15 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import javax.sql.DataSource;
@ -25,8 +25,7 @@ import java.util.stream.Stream;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
private final Environment env;
private final DataSource datasource;
@ -74,47 +73,57 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(getAuthenticationProvider());
@Bean
public SecurityFilterChain filterChain(
HttpSecurity httpSecurity, AuthenticationManager authenticationManager
) throws Exception {
return httpSecurity.authorizeHttpRequests((authz) -> {
try {
authz
.requestMatchers("/users", "/users/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(new SilentAuthenticationEntryPoint())
.and()
.cors()
.configurationSource(request -> {
var corsConfig = new CorsConfiguration();
corsConfig.applyPermitDefaultValues();
var corsDomains = environment.getProperty("twigs.cors.domains", "*");
corsConfig.setAllowedOrigins(Arrays.asList(corsDomains.split(",")));
corsConfig.setAllowedMethods(
Stream.of(
HttpMethod.GET,
HttpMethod.POST,
HttpMethod.PUT,
HttpMethod.DELETE,
HttpMethod.OPTIONS
)
.map(HttpMethod::name)
.collect(Collectors.toList())
);
corsConfig.setAllowCredentials(true);
return corsConfig;
})
.and()
.csrf()
.ignoringRequestMatchers("/users", "/users/login")
.and()
.addFilter(new TokenAuthenticationFilter(authenticationManager))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.build();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users/new", "/users/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(new SilentAuthenticationEntryPoint())
.and()
.cors()
.configurationSource(request -> {
var corsConfig = new CorsConfiguration();
corsConfig.applyPermitDefaultValues();
var corsDomains = environment.getProperty("twigs.cors.domains", "*");
corsConfig.setAllowedOrigins(Arrays.asList(corsDomains.split(",")));
corsConfig.setAllowedMethods(
Stream.of(
HttpMethod.GET,
HttpMethod.POST,
HttpMethod.PUT,
HttpMethod.DELETE,
HttpMethod.OPTIONS
)
.map(Enum::name)
.collect(Collectors.toList())
);
corsConfig.setAllowCredentials(true);
return corsConfig;
})
.and()
.csrf()
.disable()
.addFilter(new TokenAuthenticationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
@Bean
public AuthenticationManager getAuthenticationManager(AuthenticationConfiguration auth) throws Exception {
return auth.getAuthenticationManager();
}
}

View file

@ -1,11 +1,10 @@
package com.wbrawner.twigs.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
@ -15,7 +14,7 @@ public class SilentAuthenticationEntryPoint implements AuthenticationEntryPoint
@Override
public void commence(
HttpServletRequest request, HttpServletResponse response, AuthenticationException authException
) throws IOException, ServletException {
) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}

View file

@ -1,13 +1,13 @@
package com.wbrawner.twigs.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

View file

@ -1,8 +1,10 @@
package com.wbrawner.twigs.passwordresetrequest;
import com.wbrawner.twigs.user.User;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import javax.persistence.*;
import java.util.Calendar;
import java.util.GregorianCalendar;

View file

@ -2,8 +2,7 @@ package com.wbrawner.twigs.permission;
import com.wbrawner.twigs.budget.Budget;
import com.wbrawner.twigs.user.User;
import javax.persistence.*;
import jakarta.persistence.*;
@Entity
public class UserPermission {

View file

@ -1,6 +1,7 @@
package com.wbrawner.twigs.permission;
import javax.persistence.Embeddable;
import jakarta.persistence.Embeddable;
import java.io.Serializable;
@Embeddable

View file

@ -3,12 +3,14 @@ package com.wbrawner.twigs.permission;
import com.wbrawner.twigs.budget.Budget;
import com.wbrawner.twigs.user.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
import java.util.Optional;
public interface UserPermissionRepository extends PagingAndSortingRepository<UserPermission, UserPermissionKey> {
public interface UserPermissionRepository extends CrudRepository<UserPermission, UserPermissionKey>,
PagingAndSortingRepository<UserPermission, UserPermissionKey> {
Optional<UserPermission> findByUserAndBudget_Id(User user, String budgetId);
List<UserPermission> findAllByUser(User user, Pageable pageable);

View file

@ -1,7 +1,8 @@
package com.wbrawner.twigs.session;
import javax.persistence.Entity;
import javax.persistence.Id;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import java.util.Date;
import static com.wbrawner.twigs.Utils.*;

View file

@ -1,12 +1,14 @@
package com.wbrawner.twigs.session;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.Date;
import java.util.List;
import java.util.Optional;
public interface UserSessionRepository extends PagingAndSortingRepository<Session, String> {
public interface UserSessionRepository extends CrudRepository<Session, String>,
PagingAndSortingRepository<Session, String> {
List<Session> findByUserId(String userId);
Optional<Session> findByToken(String token);

View file

@ -3,8 +3,11 @@ package com.wbrawner.twigs.transaction;
import com.wbrawner.twigs.budget.Budget;
import com.wbrawner.twigs.category.Category;
import com.wbrawner.twigs.user.User;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import javax.persistence.*;
import java.time.Instant;
import static com.wbrawner.twigs.Utils.randomId;

View file

@ -6,6 +6,7 @@ import com.wbrawner.twigs.category.CategoryRepository;
import com.wbrawner.twigs.permission.Permission;
import com.wbrawner.twigs.permission.UserPermission;
import com.wbrawner.twigs.permission.UserPermissionRepository;
import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
@ -15,7 +16,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;

View file

@ -4,6 +4,7 @@ import com.wbrawner.twigs.budget.Budget;
import com.wbrawner.twigs.category.Category;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.time.Instant;
@ -11,7 +12,8 @@ import java.util.Date;
import java.util.List;
import java.util.Optional;
public interface TransactionRepository extends PagingAndSortingRepository<Transaction, String> {
public interface TransactionRepository extends CrudRepository<Transaction, String>,
PagingAndSortingRepository<Transaction, String> {
Optional<Transaction> findByIdAndBudgetIn(String id, List<Budget> budgets);
List<Transaction> findAllByBudgetInAndCategoryInAndDateGreaterThanAndDateLessThan(

View file

@ -1,20 +1,20 @@
package com.wbrawner.twigs.user;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Transient;
import org.springframework.lang.NonNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.wbrawner.twigs.Utils.randomId;
@Entity
@Entity(name = "users")
public class User implements UserDetails {
@Id
private final String id = randomId();

View file

@ -7,6 +7,7 @@ import com.wbrawner.twigs.permission.UserPermissionResponse;
import com.wbrawner.twigs.session.Session;
import com.wbrawner.twigs.session.SessionResponse;
import com.wbrawner.twigs.session.UserSessionRepository;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -18,7 +19,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

View file

@ -1,11 +1,13 @@
package com.wbrawner.twigs.user;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends PagingAndSortingRepository<User, String> {
public interface UserRepository extends CrudRepository<User, String>,
PagingAndSortingRepository<User, String> {
Optional<User> findByUsername(String username);
Optional<User> findByUsernameAndPassword(String username, String password);

View file

@ -1,10 +1,11 @@
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/budget
spring.datasource.username=budget
spring.datasource.password=budget
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/twigs
spring.datasource.username=twigs
spring.datasource.password=twigs
spring.profiles.active=prod
spring.session.jdbc.initialize-schema=always
spring.datasource.testWhileIdle=true
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.validationQuery=SELECT 1
twigs.cors.domains=*
logging.level.org.springframework.security=DEBUG

View file

@ -2,12 +2,12 @@ buildscript {
repositories {
mavenLocal()
mavenCentral()
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE"
classpath "org.springframework.boot:spring-boot-gradle-plugin:3.0.3"
}
}
@ -17,9 +17,9 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
maven { url "http://repo.maven.apache.org/maven2" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.maven.apache.org/maven2" }
}
jar {

View file

@ -8,7 +8,7 @@ services:
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: "jdbc:mysql://db:3306/budget?useSSL=false"
SPRING_DATASOURCE_URL: "jdbc:postgres://db:5432/twigs"
SPRING_JPA_HIBERNATE_DDL-AUTO: update
SERVER_TOMCAT_MAX-THREADS: 5
TWIGS_CORS_DOMAINS: "http://localhost:4200"
@ -17,14 +17,13 @@ services:
command: sh -c "sleep 5 && /opt/java/openjdk/bin/java $JVM_ARGS -jar /twigs-api.jar"
db:
image: mysql:5.7
image: postgres:13
ports:
- "3306:3306"
- "5432:5432"
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
MYSQL_DATABASE: budget
MYSQL_USER: budget
MYSQL_PASSWORD: budget
POSTGRES_DB: twigs
POSTGRES_USER: twigs
POSTGRES_PASSWORD: twigs
networks:
- twigs
hostname: db