- Добавил reCaptcha
- Добавил поддержку rememberMe и хранение сессий в БД. https://www.youtube.com/watch?v=7cDpbAbhyjc&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=14
This commit is contained in:
8
pom.xml
8
pom.xml
@@ -78,8 +78,12 @@
|
|||||||
<artifactId>flyway-mysql</artifactId>
|
<artifactId>flyway-mysql</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.session</groupId>
|
||||||
|
<artifactId>spring-session-jdbc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package net.sytes.kashey.sweater.config;
|
package net.sytes.kashey.sweater.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
@@ -12,6 +14,11 @@ public class MvcConfig implements WebMvcConfigurer {
|
|||||||
@Value("${upload.path}")
|
@Value("${upload.path}")
|
||||||
private String uploadPath;
|
private String uploadPath;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
public void addViewControllers(ViewControllerRegistry registry) {
|
public void addViewControllers(ViewControllerRegistry registry) {
|
||||||
|
|
||||||
registry.addViewController("/login").setViewName("login");
|
registry.addViewController("/login").setViewName("login");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package net.sytes.kashey.sweater.config;
|
|||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.EnableWebSecurity;
|
||||||
@@ -30,7 +31,7 @@ public class WebSecurityConfig {
|
|||||||
.loginPage("/login")
|
.loginPage("/login")
|
||||||
.defaultSuccessUrl("/main", true)
|
.defaultSuccessUrl("/main", true)
|
||||||
.permitAll()
|
.permitAll()
|
||||||
)
|
).rememberMe(Customizer.withDefaults())
|
||||||
.logout(LogoutConfigurer::permitAll);
|
.logout(LogoutConfigurer::permitAll);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
|
|||||||
@@ -2,21 +2,33 @@ package net.sytes.kashey.sweater.controller;
|
|||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import net.sytes.kashey.sweater.domain.User;
|
import net.sytes.kashey.sweater.domain.User;
|
||||||
|
import net.sytes.kashey.sweater.domain.dto.CaptchaResponseDto;
|
||||||
import net.sytes.kashey.sweater.service.CustomUserDetailsService;
|
import net.sytes.kashey.sweater.service.CustomUserDetailsService;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class RegistrationController {
|
public class RegistrationController {
|
||||||
|
|
||||||
|
private final static String CAPTCHA_URL = "https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s";
|
||||||
private final CustomUserDetailsService customUserDetailsService;
|
private final CustomUserDetailsService customUserDetailsService;
|
||||||
|
|
||||||
public RegistrationController(CustomUserDetailsService customUserDetailsService) {
|
@Value("${recaptcha.secret}")
|
||||||
|
private String recaptchaSecret;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public RegistrationController(CustomUserDetailsService customUserDetailsService, RestTemplate restTemplate) {
|
||||||
this.customUserDetailsService = customUserDetailsService;
|
this.customUserDetailsService = customUserDetailsService;
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/registration")
|
@GetMapping("/registration")
|
||||||
@@ -26,13 +38,31 @@ public class RegistrationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/registration")
|
@PostMapping("/registration")
|
||||||
public String addUser(@ModelAttribute @Valid User user, BindingResult bindingResult, Model model) {
|
public String addUser(@RequestParam(name = "g-recaptcha-response") String captchaResponse,
|
||||||
|
@RequestParam(name = "password2") String passwordConfirmation,
|
||||||
|
@ModelAttribute @Valid User user,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Model model) {
|
||||||
|
String url = String.format(CAPTCHA_URL, recaptchaSecret, captchaResponse);
|
||||||
|
CaptchaResponseDto responseDto = restTemplate.postForObject(url, Collections.EMPTY_LIST, CaptchaResponseDto.class);
|
||||||
|
|
||||||
if (user.getPassword() != null && !user.getPassword().equals(user.getPassword2())) {
|
boolean isCaptchaEmpty = !Objects.requireNonNull(responseDto).isSuccess();
|
||||||
|
|
||||||
|
if (isCaptchaEmpty) {
|
||||||
|
model.addAttribute("captchaError", "Fill the captcha");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.getPassword() != null && !user.getPassword().equals(passwordConfirmation)) {
|
||||||
model.addAttribute("passwordError", "Passwords do not match!");
|
model.addAttribute("passwordError", "Passwords do not match!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingResult.hasErrors()) {
|
boolean isConfirmationEmpty = ObjectUtils.isEmpty(passwordConfirmation);
|
||||||
|
|
||||||
|
if (isConfirmationEmpty) {
|
||||||
|
model.addAttribute("password2Error", "Password confirmation must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindingResult.hasErrors() || isConfirmationEmpty || isCaptchaEmpty) {
|
||||||
model.mergeAttributes(ControllerUtils.getErrors(bindingResult));
|
model.mergeAttributes(ControllerUtils.getErrors(bindingResult));
|
||||||
return "registration";
|
return "registration";
|
||||||
}
|
}
|
||||||
@@ -50,8 +80,10 @@ public class RegistrationController {
|
|||||||
public String activateAccount(Model model, @PathVariable String activateCode) {
|
public String activateAccount(Model model, @PathVariable String activateCode) {
|
||||||
|
|
||||||
if (customUserDetailsService.activateUser(activateCode)) {
|
if (customUserDetailsService.activateUser(activateCode)) {
|
||||||
|
model.addAttribute("messageType", "success");
|
||||||
model.addAttribute("message", "User successfully activated!");
|
model.addAttribute("message", "User successfully activated!");
|
||||||
} else {
|
} else {
|
||||||
|
model.addAttribute("messageType", "danger");
|
||||||
model.addAttribute("message", "User activation is failed!");
|
model.addAttribute("message", "User activation is failed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import jakarta.persistence.*;
|
|||||||
import jakarta.validation.constraints.Email;
|
import jakarta.validation.constraints.Email;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "usr")
|
@Table(name = "usr")
|
||||||
public class User {
|
public class User implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
@@ -18,9 +22,6 @@ public class User {
|
|||||||
@NotBlank(message = "Password must not be empty.")
|
@NotBlank(message = "Password must not be empty.")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Transient
|
|
||||||
@NotBlank(message = "Password confirmation must not be empty.")
|
|
||||||
private String password2;
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
@Email(message = "E-mail is not valid.")
|
@Email(message = "E-mail is not valid.")
|
||||||
@@ -100,12 +101,4 @@ public class User {
|
|||||||
public void setActivationCode(String activationCode) {
|
public void setActivationCode(String activationCode) {
|
||||||
this.activationCode = activationCode;
|
this.activationCode = activationCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword2() {
|
|
||||||
return password2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword2(String password2) {
|
|
||||||
this.password2 = password2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.sytes.kashey.sweater.domain.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class CaptchaResponseDto {
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
@JsonAlias("error-codes")
|
||||||
|
private Set<String> errorCodes;
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccess(boolean success) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getErrorCodes() {
|
||||||
|
return errorCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorCodes(Set<String> errorCodes) {
|
||||||
|
this.errorCodes = errorCodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,21 @@ spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/sweater_db
|
|||||||
spring.datasource.username=user
|
spring.datasource.username=user
|
||||||
spring.datasource.password=123456
|
spring.datasource.password=123456
|
||||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
spring.freemarker.expose-request-attributes=true
|
spring.freemarker.expose-request-attributes=true
|
||||||
spring.freemarker.expose-session-attributes=true
|
spring.freemarker.expose-session-attributes=true
|
||||||
spring.freemarker.suffix=.ftl
|
spring.freemarker.suffix=.ftl
|
||||||
upload.path=/home/kashey/soft/sweater/uploads
|
upload.path=/home/kashey/soft/sweater/uploads
|
||||||
hosting.path=http://localhost:8080
|
hosting.path=http://localhost:8080
|
||||||
|
|
||||||
spring.mail.host=smtp.yandex.ru
|
spring.mail.host=smtp.yandex.ru
|
||||||
spring.mail.username=username@yandex.ru
|
spring.mail.username=username@yandex.ru
|
||||||
spring.mail.password=pa$$word
|
spring.mail.password=pa$$word
|
||||||
spring.mail.port=465
|
spring.mail.port=465
|
||||||
spring.mail.protocol=smtps
|
spring.mail.protocol=smtps
|
||||||
mail.debug=true
|
mail.debug=true
|
||||||
|
|
||||||
|
recaptcha.secret=
|
||||||
|
spring.session.jdbc.serialize=java
|
||||||
|
spring.session.jdbc.initialize-schema=always
|
||||||
|
spring.session.jdbc.table-name=SPRING_SESSION
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
<#import "parts/login.ftl" as l>
|
<#import "parts/login.ftl" as l>
|
||||||
|
|
||||||
<@com.page>
|
<@com.page>
|
||||||
<#if Session?? && Session.SPRING_SECURITY_LAST_EXCEPTION??>
|
<#if SPRING_SECURITY_LAST_EXCEPTION??>
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
${Session.SPRING_SECURITY_LAST_EXCEPTION.message}
|
${SPRING_SECURITY_LAST_EXCEPTION.message}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<#if message??>
|
||||||
|
<div <#if messageType??>class="alert alert-${messageType}" role="alert"</#if> >
|
||||||
|
${message}
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
<@l.login "/login" false/>
|
<@l.login "/login" false/>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||||
|
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -53,6 +53,14 @@
|
|||||||
</#if>
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="g-recaptcha" data-sitekey="6LcB49sqAAAAAIvXz46_hEGqtYAzFYYGuY_Y9Rlg"></div>
|
||||||
|
<#if captchaError??>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
${captchaError}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
<#if !isRegistrationForm>
|
<#if !isRegistrationForm>
|
||||||
<a href="/registration" class="btn btn-primary">Add new user</a>
|
<a href="/registration" class="btn btn-primary">Add new user</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user