diff --git a/pom.xml b/pom.xml index 10c31c7..b3b0dba 100644 --- a/pom.xml +++ b/pom.xml @@ -8,6 +8,7 @@ release-hive 0.0.1-SNAPSHOT + @@ -32,13 +33,35 @@ spring-boot-starter-json 3.3.2 + + org.springframework.boot + spring-boot-starter-data-jpa + 3.3.2 + + + + + org.springframework.boot + spring-boot-starter-security + 3.3.2 + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + - - org.hibernate - hibernate-core - 6.6.0.CR1 - org.postgresql postgresql diff --git a/src/main/java/wtf/beatrice/releasehive/config/ApplicationConfiguration.java b/src/main/java/wtf/beatrice/releasehive/config/ApplicationConfiguration.java new file mode 100644 index 0000000..22d8cbd --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/config/ApplicationConfiguration.java @@ -0,0 +1,51 @@ +package wtf.beatrice.releasehive.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import wtf.beatrice.releasehive.repository.UserRepository; + +@Configuration +public class ApplicationConfiguration +{ + + private final UserRepository userRepository; + + public ApplicationConfiguration(@Autowired UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Bean + UserDetailsService userDetailsService() { + return username -> userRepository.findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + } + + @Bean + BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + + authProvider.setUserDetailsService(userDetailsService()); + authProvider.setPasswordEncoder(passwordEncoder()); + + return authProvider; + } + +} diff --git a/src/main/java/wtf/beatrice/releasehive/config/JWTAuthenticationFilter.java b/src/main/java/wtf/beatrice/releasehive/config/JWTAuthenticationFilter.java new file mode 100644 index 0000000..c226529 --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/config/JWTAuthenticationFilter.java @@ -0,0 +1,79 @@ +package wtf.beatrice.releasehive.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import wtf.beatrice.releasehive.service.JWTService; + +import java.io.IOException; + +@Component +public class JWTAuthenticationFilter extends OncePerRequestFilter +{ + private final HandlerExceptionResolver handlerExceptionResolver; + + private final JWTService jwtService; + private final UserDetailsService userDetailsService; + + public JWTAuthenticationFilter( + @Autowired JWTService jwtService, + @Autowired UserDetailsService userDetailsService, + @Autowired HandlerExceptionResolver handlerExceptionResolver) { + this.jwtService = jwtService; + this.userDetailsService = userDetailsService; + this.handlerExceptionResolver = handlerExceptionResolver; + } + + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + final String authHeader = request.getHeader("Authorization"); + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + try { + final String jwt = authHeader.substring(7); + final String userEmail = jwtService.extractUsername(jwt); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (userEmail != null && authentication == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); + + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + + filterChain.doFilter(request, response); + } catch (Exception exception) { + handlerExceptionResolver.resolveException(request, response, null, exception); + } + } +} diff --git a/src/main/java/wtf/beatrice/releasehive/config/SecurityConfiguration.java b/src/main/java/wtf/beatrice/releasehive/config/SecurityConfiguration.java new file mode 100644 index 0000000..ffb6c12 --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/config/SecurityConfiguration.java @@ -0,0 +1,71 @@ +package wtf.beatrice.releasehive.config; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration +{ + + private final AuthenticationProvider authenticationProvider; + private final JWTAuthenticationFilter jwtAuthenticationFilter; + + public SecurityConfiguration( + @Autowired JWTAuthenticationFilter jwtAuthenticationFilter, + @Autowired AuthenticationProvider authenticationProvider) + { + this.authenticationProvider = authenticationProvider; + this.jwtAuthenticationFilter = jwtAuthenticationFilter; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeHttpRequests() + .requestMatchers("/api/v1/auth/**") + .permitAll() + .anyRequest() + .authenticated() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authenticationProvider(authenticationProvider) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + + configuration.setAllowedOrigins(List.of("http://localhost:8080")); + configuration.setAllowedMethods(List.of("GET","POST")); + configuration.setAllowedHeaders(List.of("Authorization","Content-Type")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + + source.registerCorsConfiguration("/**",configuration); + + return source; + } + + + +} diff --git a/src/main/java/wtf/beatrice/releasehive/dto/LoginUserDto.java b/src/main/java/wtf/beatrice/releasehive/dto/LoginUserDto.java new file mode 100644 index 0000000..42e9e97 --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/dto/LoginUserDto.java @@ -0,0 +1,24 @@ +package wtf.beatrice.releasehive.dto; + +public class LoginUserDto +{ + private String email; + + private String password; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/wtf/beatrice/releasehive/dto/RegisterUserDto.java b/src/main/java/wtf/beatrice/releasehive/dto/RegisterUserDto.java new file mode 100644 index 0000000..5d24609 --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/dto/RegisterUserDto.java @@ -0,0 +1,34 @@ +package wtf.beatrice.releasehive.dto; + +public class RegisterUserDto +{ + private String email; + + private String password; + + private String username; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/src/main/java/wtf/beatrice/releasehive/model/ApiError.java b/src/main/java/wtf/beatrice/releasehive/model/ApiError.java deleted file mode 100644 index 794f0cd..0000000 --- a/src/main/java/wtf/beatrice/releasehive/model/ApiError.java +++ /dev/null @@ -1,33 +0,0 @@ -package wtf.beatrice.releasehive.model; - -import jakarta.persistence.Entity; - -import java.util.UUID; - -@Entity -public class ApiError -{ - UUID exceptionId; - - String message; - - public ApiError() { - exceptionId = UUID.randomUUID(); - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public UUID getExceptionId() { - return exceptionId; - } - - public void setExceptionId(UUID exceptionId) { - this.exceptionId = exceptionId; - } -} diff --git a/src/main/java/wtf/beatrice/releasehive/model/LoginResponse.java b/src/main/java/wtf/beatrice/releasehive/model/LoginResponse.java new file mode 100644 index 0000000..4ad4a98 --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/model/LoginResponse.java @@ -0,0 +1,34 @@ +package wtf.beatrice.releasehive.model; + +public class LoginResponse +{ + private String token; + + private long expiresIn; + + public String getToken() { + return token; + } + + public LoginResponse setToken(String token) { + this.token = token; + return this; + } + + public long getExpiresIn() { + return expiresIn; + } + + public LoginResponse setExpiresIn(long expiresIn) { + this.expiresIn = expiresIn; + return this; + } + + @Override + public String toString() { + return "LoginResponse{" + + "token='" + token + '\'' + + ", expiresIn=" + expiresIn + + '}'; + } +} diff --git a/src/main/java/wtf/beatrice/releasehive/model/User.java b/src/main/java/wtf/beatrice/releasehive/model/User.java index a10b0fa..f0d2786 100644 --- a/src/main/java/wtf/beatrice/releasehive/model/User.java +++ b/src/main/java/wtf/beatrice/releasehive/model/User.java @@ -2,36 +2,81 @@ package wtf.beatrice.releasehive.model; import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Collection; +import java.util.Date; +import java.util.List; import java.util.UUID; @Entity @Table(name="users") -public class User +public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID uuid; - @Column + @Column(nullable = false) private String username; - @Column + + @Column(unique = true, length = 64, nullable = false) + private String email; + + @Column(nullable = false) private String password; + @CreationTimestamp + @Column(updatable = false, name = "created_at") + private Date createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private Date updatedAt; + public UUID getUuid() { return uuid; } + @Override public String getUsername() { return username; } + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + public void setUsername(String username) { this.username = username; } + @Override + public Collection getAuthorities() { + return List.of(); + } + public String getPassword() { return password; } @@ -43,4 +88,28 @@ public class User public void setUuid(UUID uuid) { this.uuid = uuid; } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } } diff --git a/src/main/java/wtf/beatrice/releasehive/repository/UserRepository.java b/src/main/java/wtf/beatrice/releasehive/repository/UserRepository.java new file mode 100644 index 0000000..51a8cad --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/repository/UserRepository.java @@ -0,0 +1,14 @@ +package wtf.beatrice.releasehive.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import wtf.beatrice.releasehive.model.User; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/src/main/java/wtf/beatrice/releasehive/resource/AccountResource.java b/src/main/java/wtf/beatrice/releasehive/resource/AccountResource.java index 68a6841..2a73c34 100644 --- a/src/main/java/wtf/beatrice/releasehive/resource/AccountResource.java +++ b/src/main/java/wtf/beatrice/releasehive/resource/AccountResource.java @@ -1,29 +1,53 @@ package wtf.beatrice.releasehive.resource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import wtf.beatrice.releasehive.dto.LoginUserDto; +import wtf.beatrice.releasehive.dto.RegisterUserDto; +import wtf.beatrice.releasehive.model.LoginResponse; import wtf.beatrice.releasehive.model.User; import wtf.beatrice.releasehive.service.AccountService; +import wtf.beatrice.releasehive.service.JWTService; @RestController -@RequestMapping("/api/v1/users") +@RequestMapping("/api/v1/auth") public class AccountResource { private final AccountService accountService; + private final JWTService jwtService; - public AccountResource(@Autowired AccountService accountService) { + public AccountResource( + @Autowired AccountService accountService, + @Autowired JWTService jwtService) { this.accountService = accountService; + this.jwtService = jwtService; } - @PostMapping( value="/register", produces="application/json") - public String register(@RequestBody User user) + public ResponseEntity register(@RequestBody RegisterUserDto userDto) { - return accountService.registerUser(user); + User user = accountService.register(userDto); + return ResponseEntity.ok(user); } + + @PostMapping( + value="/login", + produces="application/json") + public ResponseEntity login(@RequestBody LoginUserDto userDto) + { + User authenticatedUser = accountService.login(userDto); + + String jwtToken = jwtService.generateToken(authenticatedUser); + + LoginResponse loginResponse = new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirationTime()); + + return ResponseEntity.ok(loginResponse); + } + } diff --git a/src/main/java/wtf/beatrice/releasehive/service/AccountService.java b/src/main/java/wtf/beatrice/releasehive/service/AccountService.java index 048a18e..692f728 100644 --- a/src/main/java/wtf/beatrice/releasehive/service/AccountService.java +++ b/src/main/java/wtf/beatrice/releasehive/service/AccountService.java @@ -1,9 +1,13 @@ package wtf.beatrice.releasehive.service; +import wtf.beatrice.releasehive.dto.LoginUserDto; +import wtf.beatrice.releasehive.dto.RegisterUserDto; import wtf.beatrice.releasehive.model.User; public interface AccountService { - String registerUser(User user); + User register(RegisterUserDto user); + + User login(LoginUserDto user); } diff --git a/src/main/java/wtf/beatrice/releasehive/service/AccountServiceImpl.java b/src/main/java/wtf/beatrice/releasehive/service/AccountServiceImpl.java index d9b2914..6fe138e 100644 --- a/src/main/java/wtf/beatrice/releasehive/service/AccountServiceImpl.java +++ b/src/main/java/wtf/beatrice/releasehive/service/AccountServiceImpl.java @@ -2,30 +2,58 @@ package wtf.beatrice.releasehive.service; import org.hibernate.Session; import org.hibernate.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import wtf.beatrice.releasehive.db.HibernateManager; +import wtf.beatrice.releasehive.dto.LoginUserDto; +import wtf.beatrice.releasehive.dto.RegisterUserDto; import wtf.beatrice.releasehive.model.User; +import wtf.beatrice.releasehive.repository.UserRepository; import wtf.beatrice.releasehive.util.JsonUtil; @Service public class AccountServiceImpl implements AccountService { - @Override - public String registerUser(User user) { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final AuthenticationManager authenticationManager; - if(null == user.getUsername() || user.getUsername().isEmpty()) { - return JsonUtil.spawnJsonError("Cannot register user without username"); - } - - if(null == user.getPassword() || user.getPassword().isEmpty()) { - return JsonUtil.spawnJsonError("Cannot register user without password"); - } - - Session session = HibernateManager.getSession(); - Transaction transaction = session.beginTransaction(); - session.persist(user); - transaction.commit(); - - return JsonUtil.convertToJson(user); + public AccountServiceImpl( + @Autowired UserRepository userRepository, + @Autowired AuthenticationManager authenticationManager, + @Autowired PasswordEncoder passwordEncoder + ) { + this.authenticationManager = authenticationManager; + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; } + + @Override + public User register(RegisterUserDto userDto) { + + User user = new User(); + user.setUsername(userDto.getUsername()); + user.setEmail(userDto.getEmail()); + user.setPassword(passwordEncoder.encode(userDto.getPassword())); + + return userRepository.save(user); + } + + @Override + public User login(LoginUserDto user) { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + user.getEmail(), + user.getPassword() + ) + ); + + return userRepository.findByEmail(user.getEmail()) + .orElseThrow(); + } + + } diff --git a/src/main/java/wtf/beatrice/releasehive/service/JWTService.java b/src/main/java/wtf/beatrice/releasehive/service/JWTService.java new file mode 100644 index 0000000..5e3193a --- /dev/null +++ b/src/main/java/wtf/beatrice/releasehive/service/JWTService.java @@ -0,0 +1,89 @@ +package wtf.beatrice.releasehive.service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Service +public class JWTService +{ + @Value("${security.jwt.secret-key}") + private String secretKey; + + @Value("${security.jwt.expiration-time}") + private long jwtExpiration; + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + public String generateToken(UserDetails userDetails) { + return generateToken(new HashMap<>(), userDetails); + } + + public String generateToken(Map extraClaims, UserDetails userDetails) { + return buildToken(extraClaims, userDetails, jwtExpiration); + } + + public long getExpirationTime() { + return jwtExpiration; + } + + private String buildToken( + Map extraClaims, + UserDetails userDetails, + long expiration + ) { + return Jwts + .builder() + .setClaims(extraClaims) + .setSubject(userDetails.getUsername()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSignInKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean isTokenValid(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + private Claims extractAllClaims(String token) { + return Jwts + .parserBuilder() + .setSigningKey(getSignInKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + private Key getSignInKey() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } +} diff --git a/src/main/java/wtf/beatrice/releasehive/util/JsonUtil.java b/src/main/java/wtf/beatrice/releasehive/util/JsonUtil.java index c4d0ded..a73e3ce 100644 --- a/src/main/java/wtf/beatrice/releasehive/util/JsonUtil.java +++ b/src/main/java/wtf/beatrice/releasehive/util/JsonUtil.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import wtf.beatrice.releasehive.model.ApiError; public class JsonUtil { @@ -24,12 +23,4 @@ public class JsonUtil return e.getMessage(); } } - - public static String spawnJsonError(String errorMessage) { - ApiError apiError = new ApiError(); - apiError.setMessage(errorMessage); - String error = convertToJson(apiError); - LOGGER.error(error); - return error; - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..d697a56 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,13 @@ +server.port=8080 + +spring.datasource.url=jdbc:postgresql://localhost:5432/releasehive +spring.datasource.username=relhive +spring.datasource.password=beelover + +## Hibernate properties +spring.jpa.hibernate.ddl-auto=update +spring.jpa.open-in-view=false + +security.jwt.secret-key=ed725256582a23e94f81ba36d7df498ea330c7ba978e2d8876fe135b4bb34068 +# 1h in millisecond +security.jwt.expiration-time=3600000 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..7d8b81b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,5 @@ +spring: + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration + - org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration