Merge branch 'Morgan' of https://github.com/LOBSTERVOVA/DatePlanner into balashev_vk

# Conflicts:
#	src/main/java/com/example/dateplanner/configurations/SecurityConfig.java
#	src/main/resources/static/js/site/blocks/header.js
This commit is contained in:
Lobstervova
2026-02-04 23:36:01 +03:00
10 changed files with 27 additions and 82 deletions

View File

@@ -138,6 +138,11 @@
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.20.1</version> <!-- или актуальная версия -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -12,16 +12,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
@@ -30,8 +24,7 @@ import org.springframework.security.web.server.csrf.CookieServerCsrfTokenReposit
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import reactor.core.publisher.Mono;
import java.net.URI;
@Slf4j
@@ -40,31 +33,10 @@ import java.net.URI;
@RequiredArgsConstructor
@EnableReactiveMethodSecurity
public class SecurityConfig {
private final AppUserRepository userRepository;
private final JwtService jwtService;
// Цепочка для API (Basic Auth) - CSRF отключаем
@Bean
@Order(1)
public SecurityWebFilterChain apiFilterChain(ServerHttpSecurity http) {
return http
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**"))
.authorizeExchange(exchange -> exchange
.pathMatchers("/api/mobile/login").permitAll()
.anyExchange().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
.csrf(ServerHttpSecurity.CsrfSpec::disable) // CSRF отключаем для API
.authenticationManager(reactiveAuthenticationManager())
.build();
}
// Цепочка для веб-интерфейса (Form Login) - CSRF включаем
@Bean
@Order(2)
public SecurityWebFilterChain webFilterChain(ServerHttpSecurity http) {
public SecurityWebFilterChain filterChain(ServerHttpSecurity http){
ServerCsrfTokenRequestAttributeHandler requestHandler = new ServerCsrfTokenRequestAttributeHandler();
requestHandler.setTokenFromMultipartDataEnabled(true);
@@ -74,13 +46,13 @@ public class SecurityConfig {
ErrorHandlingFilter errorHandlingFilter = new ErrorHandlingFilter();
return http
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/account/**"))
.csrf(csrf -> csrf.csrfTokenRequestHandler(requestHandler).csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterAfter(errorHandlingFilter, SecurityWebFiltersOrder.AUTHENTICATION)
//.addFilterAfter(errorHandlingFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange(exchange -> exchange
.pathMatchers("/account/login").permitAll()
.anyExchange().authenticated()
.pathMatchers("/account/login","/error","/error/**").permitAll()
.pathMatchers("/account/**").authenticated()
.anyExchange().permitAll()
)
.formLogin(loginSpec -> loginSpec.loginPage("/account/login").authenticationSuccessHandler(authenticationSuccessHandler()))
.logout(logoutSpec -> logoutSpec.logoutSuccessHandler(logoutSuccessHandler()))
@@ -88,19 +60,6 @@ public class SecurityConfig {
.build();
}
// Цепочка для всех остальных путей (публичные)
@Bean
@Order(3)
public SecurityWebFilterChain defaultFilterChain(ServerHttpSecurity http) {
return http
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/**"))
.authorizeExchange(exchange -> exchange
.anyExchange().permitAll()
)
// .csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
}
@Bean
public ServerRequestCache serverRequestCache() {
return new WebSessionServerRequestCache();
@@ -129,33 +88,12 @@ public class SecurityConfig {
}
@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager = new UserDetailsRepositoryReactiveAuthenticationManager(userService());
manager.setPasswordEncoder(passwordEncoder());
manager.setUserDetailsPasswordService(userDetailsPasswordService());
return manager;
}
@Bean
public ReactiveUserDetailsPasswordService userDetailsPasswordService() {
return new ReactiveUserDetailsPasswordService() {
@Override
public Mono<UserDetails> updatePassword(UserDetails user, String newPassword) {
return userRepository.findByPhone(user.getUsername()).flatMap(appUser -> {
appUser.setPassword(passwordEncoder().encode(newPassword));
return userRepository.save(appUser);
});
}
};
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public UserService userService(){
return new UserService(userRepository,passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

View File

@@ -29,7 +29,6 @@ public class JwtAuthenticationManager implements ReactiveAuthenticationManager {
//log.info("start auth manager");
String accessToken = authentication.getPrincipal().toString();
String refreshToken = authentication.getCredentials().toString();
log.info("refresh is {},{}", accessToken, refreshToken);
if(jwt.validateToken(accessToken)){
//log.info("access token is valid");
@@ -105,7 +104,7 @@ public class JwtAuthenticationManager implements ReactiveAuthenticationManager {
}
private Mono<Authentication> baseAuth(Authentication authentication){
log.info("access and refresh token is not valid - try authenticate by basic login");
//log.info("access and refresh token is not valid - try authenticate by basic login");
String username = authentication.getPrincipal().toString();
String password = authentication.getCredentials().toString();
return userService.findByUsername(username).flatMap(user -> {

View File

@@ -48,7 +48,7 @@ public class AppUser implements UserDetails {
@Override
public String getUsername() {
return this.phone;
return getPhone();
}
@Override

View File

@@ -9,4 +9,5 @@ import java.util.UUID;
public interface AppUserRepository extends R2dbcRepository<AppUser, UUID> {
Mono<AppUser> findByPhone(String phone);
Mono<AppUser> findByUsername(String username);
}

View File

@@ -2,20 +2,22 @@ package com.example.dateplanner.services;
import com.example.dateplanner.repositories.AppUserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
@Slf4j
@RequiredArgsConstructor
public class UserService implements ReactiveUserDetailsService {
private final AppUserRepository appUserRepository;
private final AppUserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Override
public Mono<UserDetails> findByUsername(String username) {
return appUserRepository.findByPhone(username).flatMap(Mono::just).cast(UserDetails.class);
log.info("username {}", username);
return userRepository.findByPhone(username).flatMap(Mono::just).cast(UserDetails.class);
}
}

View File

@@ -11,7 +11,7 @@ public class CookieUtil {
private final String SESSION;
protected CookieUtil(){
String appName = "pstweb";
String appName = "dp";
REFRESH = appName + "-refresh";
ACCESS = appName + "-access";
SESSION = appName + "-session";

View File

@@ -23,7 +23,7 @@ spring.flyway.locations=db/migration
spring.flyway.validate-migration-naming=true
spring.flyway.baseline-on-migrate=true
#====================== minio configuration =====================
minio.bucket=
minio.bucket=${spring.application.name}
minio.url=
minio.cdn=
minio.username=

View File

@@ -103,14 +103,14 @@ function initHeader($header){
<!-- Форма входа -->
<div class="tab-pane fade show active" id="login" role="tabpanel">
<form id="loginForm" method="POST" action="/account/login">
<input type="hidden" name="${csrf.parameterName}" value="${csrf.token}" />
<input type="hidden" name="_csrf" value="${csrf.token}"/>
<div class="mb-3">
<label class="form-label">Email или телефон</label>
<input type="text" class="form-control" id="loginEmail" placeholder="example@mail.ru" required>
<input type="text" class="form-control" id="loginEmail" placeholder="example@mail.ru" name="username" required>
</div>
<div class="mb-3">
<label class="form-label">Пароль</label>
<input type="password" class="form-control" id="loginPassword" required>
<input type="password" class="form-control" id="loginPassword" name="password" required>
<div class="text-end mt-2">
<a href="#" class="forgot-password" onclick="showForgotPassword()">Забыли пароль?</a>
</div>