#7 Добавила spring security.

Настроила аутентификацию пользователей.
Написать миграцию для добавления в таблицы пользователей учетной записи админа.
Отрисовала страницы регистрации, аутентификации, кабинета
Создала методы в контроллере и сервисе
parent ab07e875
......@@ -40,6 +40,11 @@
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.5.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
......@@ -55,6 +60,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
......
package com.example.final_exam_l.configuration;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.sql.DataSource;
@Configuration
@AllArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final DataSource _db;
@Bean
public PasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
String fetchUsersQuery = "select email, password, active " +
"as enabled from users " +
"where email = ?";
String fetchRolesQuery = "select email, role " +
"from users " +
"where email = ?";
auth.jdbcAuthentication()
.dataSource(_db)
.usersByUsernameQuery(fetchUsersQuery)
.authoritiesByUsernameQuery(fetchRolesQuery);
}
@Override
protected void configure(HttpSecurity http) throws Exception{
http.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true");
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.clearAuthentication(true)
.invalidateHttpSession(true);
http.authorizeRequests()
.antMatchers("/cabinet")
.authenticated();
http.authorizeRequests()
.anyRequest()
.permitAll();
}
}
package com.example.final_exam_l.configuration;
import com.example.final_exam_l.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, WebMvcConfigurer {
private final UserRepository userRepository;
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.addErrorPages(
new ErrorPage(HttpStatus.FORBIDDEN, "/403"),
new ErrorPage(HttpStatus.NOT_FOUND, "/404"),
new ErrorPage(HttpStatus.METHOD_NOT_ALLOWED, "/405"),
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500"));
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PutUserInModelInterceptor(userRepository));
}
}
package com.example.final_exam_l.controller;
import com.example.final_exam_l.dto.add.UserAddDTO;
import com.example.final_exam_l.service.UserService;
import lombok.RequiredArgsConstructor;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
import java.security.Principal;
@Controller
@RequiredArgsConstructor
public class UserController {
private final UserService service;
@GetMapping("/register")
public String register(){
return "user/register";
}
@PostMapping("/register")
public String register(@Valid UserAddDTO dto,
BindingResult result,
RedirectAttributes attributes){
attributes.addFlashAttribute("dto", dto);
if (result.hasFieldErrors()){
attributes.addFlashAttribute("errors", result.getFieldErrors());
return "redirect:/register";
}
else if (service.existByEmail(dto.getEmail())){
attributes.addFlashAttribute("emailError", "Пользователь с мейлом "
+ dto.getEmail() + " существует. Выберите другой");
return "redirect:/register";
}
else if (service.existByLogin(dto.getLogin())){
attributes.addFlashAttribute("loginError", "Пользователь с логином "
+ dto.getLogin() + " существует. Выберите другой");
return "redirect:/register";
}
service.register(dto);
attributes.addFlashAttribute("success", "Регистрация прошла успешно! Вам необходимо авторизоваться");
return "redirect:/login";
}
@GetMapping("/login")
public String login(@RequestParam(required = false, defaultValue = "false") Boolean error, Model model){
model.addAttribute("error", error);
return "user/login";
}
@GetMapping("/cabinet")
public String cabinet(Model model, Principal principal){
model.addAttribute("user", service.findByEmail(principal.getName()));
return "user/cabinet";
}
@PostMapping("/logout")
public String logOut(){
return "redirect:/login";
}
}
package com.example.final_exam_l.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Getter
@AllArgsConstructor
public class UserLoginDTO {
@NotNull(message = "Введите корректный мэйл")
@Pattern(regexp = "([a-zA-Z0-9]+(?:[._+-][a-zA-Z0-9]+)*)@([a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*[.][a-zA-Z]{2,})", message = "Введите корректный мэйл")
private String email;
@NotNull(message = "Введите пароль")
private String password;
}
package com.example.final_exam_l.dto.add;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.*;
@Getter
@AllArgsConstructor
public class UserAddDTO {
@NotNull(message = "Введите логин")
@Size(min = 3, message = "Логин должен содержать не менее 3 букв")
private String login;
@NotNull(message = "Введите корректный мэйл")
@Pattern(regexp = "([a-zA-Z0-9]+(?:[._+-][a-zA-Z0-9]+)*)@([a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*[.][a-zA-Z]{2,})", message = "Введите корректный мэйл")
private String email;
@NotNull(message = "Введите корректный пароль")
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=\\S+$).{8,}$", message = "Пароль должен содержать букву в верхнем регистре, нижнем регистре, цифру, длина - 8 символов")
private String password;
}
package com.example.final_exam_l.entity;
import com.example.final_exam_l.enumiration.UserRole;
import com.sun.istack.NotNull;
import javax.validation.constraints.NotNull;
import lombok.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Table(name = "users")
@Entity
......@@ -39,4 +41,9 @@ public class User {
@NotNull
@Builder.Default
private boolean active = true;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
@Builder.Default
@ToString.Exclude
List<Place> places = new ArrayList<>();
}
......@@ -4,7 +4,11 @@ import com.example.final_exam_l.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
boolean existsByLogin(String login);
}
package com.example.final_exam_l.service;
import com.example.final_exam_l.dto.add.UserAddDTO;
import com.example.final_exam_l.entity.User;
import com.example.final_exam_l.exception.ResourceNotFoundException;
import com.example.final_exam_l.exception.UserAlreadyExistsException;
import com.example.final_exam_l.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repository;
private final PasswordEncoder encoder;
public User register(UserAddDTO dto){
if(repository.existsByEmail(dto.getEmail()) || repository.existsByLogin(dto.getLogin()))
throw new UserAlreadyExistsException();
User user = User.builder()
.login(dto.getLogin())
.email(dto.getEmail())
.password(encoder.encode(dto.getPassword()))
.build();
return repository.save(user);
}
public User findByEmail(String email){
return repository.findByEmail(email).orElseThrow(() -> {
return new ResourceNotFoundException("Пользователь", email);
});
}
public boolean existByEmail(String email){
return repository.existsByEmail(email);
}
public boolean existByLogin(String login){
return repository.existsByLogin(login);
}
}
use `final_exam`;
insert into `users` (login, email, password, role) VALUES
#пароль Admin123
('admin', 'admin@test.com', '$2a$10$lOwhRkS64B3sbYsXnpMLPu.ua0FUR3Yz3omdWU..V8Aqp2xzTBKqi', 'ADMIN');
\ No newline at end of file
body{
background-color: cadetblue;
}
*{
margin: 0;
padding: 0;
}
.container{
max-width: 1008px;
margin: 0 auto;
}
a{
text-decoration: none;
}
.navbar{
background-color: #bebebf;
min-height: 75px;
}
.flex-between{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.flex{
display: flex;
flex-direction: row;
justify-content: space-around;
width: 30%;
}
.nav-item{
}
.card{
margin: 0 auto;
text-align: center;
}
.form{
display: flex;
margin: 0 auto;
align-items: center;
justify-content: center
}
label{
display: block;
}
.col{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center
}
.info-div{
text-align: center;
}
\ No newline at end of file
<#macro footer>
<#nested>
<footer class=" text-center text-white mt-3" style="background-color:#476393;">
<div class="container p-4">
<section class="mb-4">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt
distinctio earum repellat quaerat voluptatibus placeat nam,
commodi optio pariatur est quia magnam eum harum corrupti dicta,
aliquam sequi voluptate quas.
</p>
</section>
</div>
<div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2)">
© 2020 Copyright:
<a class="text-white" href="https://mdbootstrap.com/">MDBootstrap.com</a>
</div>
</footer>
</#macro>
\ No newline at end of file
<#import "navbar.ftlh" as navbar/>
<#import "footer.ftlh" as footer/>
<#macro renderWith title="" scripts=[] styles=[]>
<!doctype html>
<html lang="en">
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<base href="/">
<link href='https://fonts.googleapis.com/css?family=Roboto:400,100,300,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/main.css">
<#list styles as styles>
<link rel="stylesheet" href="${styles}">
</#list>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<title>${title}</title>
</head>
<body>
<@navbar.navbar>
</@navbar.navbar>
<#nested>
<@footer.footer>
</@footer.footer>
<script src="js/jquery-3.6.0.min.js"></script>
<script src="js/bootstrap.bundle.min.js"></script>
<script src="js/main.js"></script>
<#list scripts as scripts>
<script src="${scripts}"></script>
</#list>
</body>
</html>
</#macro>
\ No newline at end of file
<#macro navbar>
<nav class="navbar navbar-expand-lg navbar-dark navbar-custom">
<div class="container">
<div class="navbar flex-between">
<div class="flex">
<div class="nav-item">
<a href="/">Заведения</a>
</div>
<div class="nav-item">
<a href="/places">Добавить завeдение</a>
</div>
</div>
<div class="flex">
<#if !(user??)>
<div class="nav-item">
<a class="nav-link" href="/login">Вход</a>
</div>
<#else>
<div class="nav-item">
<span>${user.email}</span>
</div>
<div class="nav-item">
<form action="/logout" method="post">
<input type='hidden' value='${_csrf.token}' name='${_csrf.parameterName}'/>
<button type="submit" class="btn nav-link">Выход</button>
</form>
</div>
</#if>
</div>
</div>
</div>
</nav>
<#nested>
</#macro>
\ No newline at end of file
<#import "../main.ftlh" as main/>
<@main.renderWith title="${user.login}">
<div class="container">
<div class="info-div">
<h1>Пользователь ${user.login}</h1>
<div class="col">
<p class="info">Email: ${user.email}</p>
<p class="info">Добавленные заведения:</p>
<#list user.places as p>
<a href="/places/${p.id}">${p.name}</a>
</#list>
</div>
</div>
</div>
</@main.renderWith>
\ No newline at end of file
<#import "../main.ftlh" as main/>
<@main.renderWith title="Аутентификация">
<div class="container">
<div class="card">
<h1>Аутентификация пользователя</h1>
<#if login??>
<h3 class="error">${login}</h3>
</#if>
<div class="form">
<form action="/login" method="post">
<div>
<#if error!false >
<p style="color:red">Неверные имя пользователя или пароль !</p>
</#if>
</div>
<input type='hidden' value='${_csrf.token}' name='${_csrf.parameterName}'/>
<div class="col">
<label for="email" class="form-label fs-4">Email</label>
<input type="email" name="username" class="form-control" id="email" value="${(dto.email)!''}">
</div>
<div class="col">
<label for="password" class="form-label fs-4">Пароль</label>
<input type="password" name="password" class="form-control" id="password">
</div>
<div class="col">
<button type="submit" class="btn btn-primary mt-1" id="btn">Войти</button>
</div>
</form>
</div>
</div>
</div>
</@main.renderWith>
\ No newline at end of file
<#import "../main.ftlh" as main/>
<@main.renderWith title="Регистрация">
<div class="container">
<div class="card">
<h1>Регистрация пользователя</h1>
<div class="form">
<form action="/register" method="post">
<input type='hidden' value='${_csrf.token}' name='${_csrf.parameterName}'/>
<div class="col">
<label for="login" class="form-label fs-4">Логин</label>
<input type="login" name="login" class="form-control" id="email" value="${(dto.login)!''}">
</div>
<div class="col">
<label for="email" class="form-label fs-4">Email</label>
<input type="email" name="email" class="form-control" id="email" value="${(dto.email)!''}">
</div>
<div class="col">
<label for="password" class="form-label fs-4">Пароль</label>
<input type="password" name="password" class="form-control" id="password">
</div>
<button type="submit" class="btn btn-primary mt-1" id="btn">Регистрация</button>
</form>
</div>
</div>
<div class="error_list">
<#if errors??>
<#list errors as e>
<h2>${e.defaultMessage!'__no message__'}</h2>
</#list>
<#elseif loginError??>
<h2>${loginError}</h2>
<#elseif emailError??>
<h2>${emailError}</h2>
</#if>
</div>
</div>
</@main.renderWith>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment