Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
beab30cb44
|
|||
|
9339a1b7b1
|
|||
|
baeeed40d1
|
11
build.gradle
11
build.gradle
@@ -28,12 +28,23 @@ dependencies {
|
|||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'org.springframework.security:spring-security-core'
|
||||||
|
implementation 'org.springframework.security:spring-security-web'
|
||||||
|
implementation 'org.springframework.security:spring-security-config'
|
||||||
|
implementation 'org.springframework.security:spring-security-crypto'
|
||||||
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
|
|
||||||
implementation 'org.hibernate.orm:hibernate-community-dialects'
|
implementation 'org.hibernate.orm:hibernate-community-dialects'
|
||||||
implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
|
implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
|
||||||
|
implementation 'com.h2database:h2'
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.lionarius.isdojplab.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ApplicationConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebClient webClient() {
|
||||||
|
return WebClient.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package ru.lionarius.isdojplab.config;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.jwk.JWKSet;
|
||||||
|
import com.nimbusds.jose.jwk.RSAKey;
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||||
|
import com.nimbusds.jose.proc.SecurityContext;
|
||||||
|
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.config.Customizer;
|
||||||
|
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.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class AuthorizationServerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||||
|
http.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.formLogin(Customizer.withDefaults());
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
|
||||||
|
var secret = passwordEncoder.encode("secret");
|
||||||
|
System.out.println("secret: " + secret);
|
||||||
|
|
||||||
|
RegisteredClient loginClient = RegisteredClient
|
||||||
|
.withId(UUID.randomUUID().toString())
|
||||||
|
.clientId("login-client")
|
||||||
|
.clientSecret(secret)
|
||||||
|
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||||
|
.redirectUri("http://localhost:8080/login/oauth2/code/login-client")
|
||||||
|
.redirectUri("http://localhost:8080/")
|
||||||
|
.scope("read")
|
||||||
|
.scope("write")
|
||||||
|
.clientSettings(ClientSettings.builder()
|
||||||
|
.requireAuthorizationConsent(true)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.tokenSettings(TokenSettings.builder()
|
||||||
|
.accessTokenTimeToLive(Duration.ofDays(1))
|
||||||
|
.refreshTokenTimeToLive(Duration.ofDays(1))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new InMemoryRegisteredClientRepository(loginClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthorizationServerSettings authorizationServerSettings() {
|
||||||
|
return AuthorizationServerSettings.builder()
|
||||||
|
.issuer("http://localhost:8080")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JWKSource<SecurityContext> jwkSource() {
|
||||||
|
RSAKey rsaKey = generateRsa();
|
||||||
|
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||||
|
return (jwkSelect, securityContext) -> jwkSelect.select(jwkSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||||
|
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RSAKey generateRsa() {
|
||||||
|
KeyPair keyPair = generateRsaKey();
|
||||||
|
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||||
|
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||||
|
return new RSAKey.Builder(publicKey)
|
||||||
|
.privateKey(privateKey)
|
||||||
|
.keyID(UUID.randomUUID().toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyPair generateRsaKey() {
|
||||||
|
KeyPair keyPair;
|
||||||
|
try {
|
||||||
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
keyPairGenerator.initialize(2048);
|
||||||
|
keyPair = keyPairGenerator.generateKeyPair();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package ru.lionarius.isdojplab.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/").permitAll()
|
||||||
|
.requestMatchers("/login/**").permitAll()
|
||||||
|
.requestMatchers("/oauth2/**").permitAll()
|
||||||
|
.requestMatchers("/register").permitAll()
|
||||||
|
.requestMatchers("/logout").permitAll()
|
||||||
|
.requestMatchers("/css/**").permitAll()
|
||||||
|
.requestMatchers("/js/**").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.GET, "/api/**").hasAuthority("scope_read")
|
||||||
|
.requestMatchers("/api/**").hasAuthority("scope_write")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.oauth2ResourceServer(oauth2 -> {
|
||||||
|
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()));
|
||||||
|
});
|
||||||
|
|
||||||
|
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));
|
||||||
|
http.logout(logout -> logout.logoutSuccessUrl("/").permitAll());
|
||||||
|
|
||||||
|
http
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
|
||||||
|
.securityContext(securityContext -> securityContext
|
||||||
|
.securityContextRepository(new HttpSessionSecurityContextRepository())
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||||
|
var converter = new JwtAuthenticationConverter();
|
||||||
|
|
||||||
|
var authoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||||
|
authoritiesConverter.setAuthoritiesClaimName("scope");
|
||||||
|
authoritiesConverter.setAuthorityPrefix("scope_");
|
||||||
|
|
||||||
|
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
|
||||||
|
var provider = new DaoAuthenticationProvider(passwordEncoder);
|
||||||
|
provider.setUserDetailsService(userDetailsService);
|
||||||
|
|
||||||
|
return new ProviderManager(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.lionarius.isdojplab.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "store")
|
||||||
|
public class StoreProperties {
|
||||||
|
private String name = "Магазин электротоваров";
|
||||||
|
private String description = "Все для вашего дома и офиса";
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ public class CartController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String showCart(Model model) {
|
public String showCart(Model model) {
|
||||||
|
var cart = new Cart();
|
||||||
|
model.addAttribute("cart", cart);
|
||||||
|
|
||||||
return "cart";
|
return "cart";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import ru.lionarius.isdojplab.model.Product;
|
|
||||||
import ru.lionarius.isdojplab.repository.ProductRepository;
|
import ru.lionarius.isdojplab.repository.ProductRepository;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class HomeController {
|
public class HomeController {
|
||||||
|
|
||||||
@Autowired
|
private final ProductRepository productRepository;
|
||||||
private ProductRepository productRepository;
|
|
||||||
|
public HomeController(ProductRepository productRepository) {
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String home(Model model) {
|
public String home(Model model) {
|
||||||
model.addAttribute("message", "Добро пожаловать в магазин электротоваров!");
|
model.addAttribute("message", "Добро пожаловать в магазин электротоваров!");
|
||||||
|
|
||||||
List<Product> products = productRepository.findAll();
|
var products = productRepository.findAll();
|
||||||
|
|
||||||
model.addAttribute("products", products);
|
model.addAttribute("products", products);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package ru.lionarius.isdojplab.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import ru.lionarius.isdojplab.model.User;
|
||||||
|
import ru.lionarius.isdojplab.repository.UserRepository;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class LoginFormController {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
public LoginFormController(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.authenticationManager = authenticationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
public String showForm(Model model) {
|
||||||
|
model.addAttribute("user", new User());
|
||||||
|
|
||||||
|
return "login_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public String submitForm(@ModelAttribute("user") User user, BindingResult bindingResult, Model model, HttpSession httpSession) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
return "login_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
var userFromDb = userRepository.findByEmail(user.getEmail());
|
||||||
|
|
||||||
|
if (userFromDb.isEmpty()) {
|
||||||
|
bindingResult.rejectValue("email", "email.not.found", "Пользователь не найден");
|
||||||
|
return "login_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(user.getPassword(), userFromDb.get().getPassword())) {
|
||||||
|
bindingResult.rejectValue("password", "password.incorrect", "Неверный пароль");
|
||||||
|
return "login_form";
|
||||||
|
}
|
||||||
|
|
||||||
|
model.addAttribute("user", userFromDb.get());
|
||||||
|
|
||||||
|
var authCredentials = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());
|
||||||
|
var auth = authenticationManager.authenticate(authCredentials);
|
||||||
|
var context = SecurityContextHolder.getContext();
|
||||||
|
context.setAuthentication(auth);
|
||||||
|
httpSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
|
||||||
|
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/logout")
|
||||||
|
public String logout(HttpSession httpSession) {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
httpSession.invalidate();
|
||||||
|
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package ru.lionarius.isdojplab.controller;
|
package ru.lionarius.isdojplab.controller;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
@@ -14,8 +14,13 @@ import ru.lionarius.isdojplab.repository.UserRepository;
|
|||||||
@Controller
|
@Controller
|
||||||
public class RegisterFormController {
|
public class RegisterFormController {
|
||||||
|
|
||||||
@Autowired
|
private final UserRepository userRepository;
|
||||||
private UserRepository userRepository;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public RegisterFormController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/register")
|
@GetMapping("/register")
|
||||||
public String showForm(Model model) {
|
public String showForm(Model model) {
|
||||||
@@ -40,10 +45,13 @@ public class RegisterFormController {
|
|||||||
return "register_form";
|
return "register_form";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hashedPassword = passwordEncoder.encode(user.getPassword());
|
||||||
|
user.setPassword(hashedPassword);
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
model.addAttribute("message", "Пользователь успешно зарегистрирован");
|
model.addAttribute("message", "Пользователь успешно зарегистрирован");
|
||||||
|
|
||||||
return "register_form";
|
return "redirect:/login";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ru.lionarius.isdojplab.model;
|
package ru.lionarius.isdojplab.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|||||||
@@ -8,10 +8,16 @@ import jakarta.validation.constraints.Email;
|
|||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Entity
|
@Entity
|
||||||
public class User {
|
public class User implements UserDetails {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
@@ -26,4 +32,14 @@ public class User {
|
|||||||
@NotBlank(message = "Поле не может быть пустым")
|
@NotBlank(message = "Поле не может быть пустым")
|
||||||
@Size(min = 6, message = "Пароль должен состоять не менее 6 символов")
|
@Size(min = 6, message = "Пароль должен состоять не менее 6 символов")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return List.of(new SimpleGrantedAuthority("SIMPLE_USER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package ru.lionarius.isdojplab.repository;
|
package ru.lionarius.isdojplab.repository;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import ru.lionarius.isdojplab.model.Product;
|
import ru.lionarius.isdojplab.model.Product;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ProductRepository extends JpaRepository<Product, Long> {
|
public interface ProductRepository extends CrudRepository<Product, Long> {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package ru.lionarius.isdojplab.repository;
|
package ru.lionarius.isdojplab.repository;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import ru.lionarius.isdojplab.model.User;
|
import ru.lionarius.isdojplab.model.User;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends CrudRepository<User, Long> {
|
||||||
|
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
|
|
||||||
Optional<User> findByName(String name);
|
Optional<User> findByName(String name);
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/main/java/ru/lionarius/isdojplab/rest/OAuth2Token.java
Normal file
34
src/main/java/ru/lionarius/isdojplab/rest/OAuth2Token.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.lionarius.isdojplab.rest;
|
||||||
|
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class OAuth2Token {
|
||||||
|
|
||||||
|
private final WebClient webClient;
|
||||||
|
|
||||||
|
public OAuth2Token(WebClient webClient) {
|
||||||
|
this.webClient = webClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/login/oauth2/code/login-client", produces = "application/json")
|
||||||
|
public String handle(String code) {
|
||||||
|
var form = new LinkedMultiValueMap<String, String>();
|
||||||
|
form.add("grant_type", "authorization_code");
|
||||||
|
form.add("code", code);
|
||||||
|
form.add("redirect_uri", "http://localhost:8080/login/oauth2/code/login-client");
|
||||||
|
|
||||||
|
return webClient.post()
|
||||||
|
.uri("http://localhost:8080/oauth2/token")
|
||||||
|
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("login-client:secret".getBytes()))
|
||||||
|
.bodyValue(form)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class)
|
||||||
|
.block();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package ru.lionarius.isdojplab.rest;
|
||||||
|
|
||||||
|
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
|
||||||
|
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
|
||||||
|
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.lionarius.isdojplab.model.User;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RepositoryEventHandler
|
||||||
|
public class UserEventHandler {
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public UserEventHandler(PasswordEncoder passwordEncoder) {
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HandleBeforeCreate
|
||||||
|
public void handleBeforeCreate(User user) {
|
||||||
|
encodePassword(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HandleBeforeSave
|
||||||
|
public void handleBeforeSave(User user) {
|
||||||
|
encodePassword(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodePassword(User user) {
|
||||||
|
if (user.getPassword() != null) {
|
||||||
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.lionarius.isdojplab.runner;
|
||||||
|
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.lionarius.isdojplab.model.Product;
|
||||||
|
import ru.lionarius.isdojplab.repository.ProductRepository;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Profile("dev")
|
||||||
|
public class DevDataInitializer implements CommandLineRunner {
|
||||||
|
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
|
||||||
|
public DevDataInitializer(ProductRepository productRepository) {
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
productRepository.save(new Product(null, "Тестовый товар 1", 1000.0, "Описание товара", null));
|
||||||
|
productRepository.save(new Product(null, "Тестовый товар 2", 2000.0, "Описание товара", null));
|
||||||
|
productRepository.save(new Product(null, "Тестовый товар 3", 3000.0, "Описание товара", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.lionarius.isdojplab.service;
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.lionarius.isdojplab.repository.UserRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class MyUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public MyUserDetailsService(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||||
|
return userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(email));
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/main/resources/application-dev.properties
Normal file
6
src/main/resources/application-dev.properties
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
spring.datasource.driver-class-name=org.sqlite.JDBC
|
||||||
|
spring.datasource.url=jdbc:sqlite::memory:
|
||||||
|
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
||||||
|
spring.jpa.generate-ddl=true
|
||||||
|
spring.jpa.hibernate.ddl-auto=create-drop
|
||||||
|
spring.jpa.show-sql=true
|
||||||
6
src/main/resources/application-prod.properties
Normal file
6
src/main/resources/application-prod.properties
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
spring.datasource.driver-class-name=org.sqlite.JDBC
|
||||||
|
spring.datasource.url=jdbc:sqlite:./database/data.db
|
||||||
|
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
||||||
|
spring.jpa.generate-ddl=true
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.show-sql=false
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
|
spring.profiles.active=dev
|
||||||
spring.application.name=isdojp-lab
|
spring.application.name=isdojp-lab
|
||||||
spring.datasource.driver-class-name=org.sqlite.JDBC
|
spring.data.rest.base-path=/api
|
||||||
spring.datasource.url=jdbc:sqlite:./database/data.db
|
|
||||||
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
store.name=asdfasfsdfs
|
||||||
spring.jpa.generate-ddl=true
|
store.description=\u0412\u0441\u0435 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0433\u043e \u0434\u043e\u043c\u0430 \u0438 \u043e\u0444\u0438\u0441\u0430
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
|
||||||
spring.jpa.show-sql=true
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html xmlns:th="http://www.thymeleaf.org">
|
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -9,14 +9,22 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1>Магазин электротоваров</h1>
|
<h1 th:text="${@storeProperties.name}"></h1>
|
||||||
<p>Все для вашего дома и офиса</p>
|
<p th:text="${@storeProperties.description}"></p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div sec:authorize="!isAuthenticated()">
|
||||||
<div class="register-container">
|
<div class="register-container">
|
||||||
<a href="/register" class="register-button">Регистрация</a>
|
<a href="/register" class="register-button">Регистрация</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div sec:authorize="isAuthenticated()">
|
||||||
|
<div class="register-container">
|
||||||
|
<a href="/logout" class="register-button">Выход</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Каталог товаров</h2>
|
<h2>Каталог товаров</h2>
|
||||||
|
|
||||||
|
|||||||
42
src/main/resources/templates/login_form.html
Normal file
42
src/main/resources/templates/login_form.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Авторизация</title>
|
||||||
|
<link rel="stylesheet" th:href="@{/css/styles.css}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>Авторизация пользователя</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<form th:action="@{/login}" th:object="${user}" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="email" id="email" th:field="*{email}" />
|
||||||
|
<br />
|
||||||
|
<span style="color: red" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="password">Пароль:</label>
|
||||||
|
<input type="password" id="password" th:field="*{password}" />
|
||||||
|
<br />
|
||||||
|
<span style="color: red" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Авторизироваться</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p th:if="${message}" th:text="${message}"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2024 Магазин электротоваров. Все права защищены.</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -39,8 +39,10 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p th:if="${message}" th:text="${message}"></p>
|
<p th:if="${message}" th:text="${message}"></p>
|
||||||
|
<a href="/login">Уже зарегистрированы?</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>© 2024 Магазин электротоваров. Все права защищены.</p>
|
<p>© 2024 Магазин электротоваров. Все права защищены.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user