This commit is contained in:
mfnefd 2024-07-20 15:48:35 +04:00
parent e67fa9e3f2
commit 2006d34fd5
44 changed files with 1063 additions and 16 deletions

View File

@ -33,6 +33,13 @@ dependencies {
implementation 'org.webjars:jquery:3.7.1'
// https://mvnrepository.com/artifact/org.webjars/popper.js
implementation 'org.webjars:popper.js:2.11.7'
implementation "org.springframework.boot:spring-boot-starter-security"
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
}

View File

@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.4.0",
"bootstrap": "^5.2.3",
"jwt-decode": "^4.0.0",
"vue": "^3.2.47",
"vue-multiselect": "^3.0.0-beta.3",
"vue-router": "^4.2.2"
@ -662,6 +663,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/magic-string": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",

View File

@ -11,6 +11,7 @@
"dependencies": {
"axios": "^1.4.0",
"bootstrap": "^5.2.3",
"jwt-decode": "^4.0.0",
"vue": "^3.2.47",
"vue-multiselect": "^3.0.0-beta.3",
"vue-router": "^4.2.2"

View File

@ -22,7 +22,7 @@
</div>
</div>
<div class="account clickable-element py-4 ml-auto">
<router-link to="/account">
<router-link :to="{name: 'Account'}">
<img src="../assets/header/account.png">
</router-link>
</div>

View File

@ -0,0 +1,19 @@
<template>
<div>
<h1>Аккаунт</h1>
<button @click="logout" class="btn btn-danger">Log Out</button>
</div>
</template>
<script>
import {removeToken} from "../services/userService";
export default {
methods: {
logout() {
removeToken();
this.$router.push("/login");
}
}
}
</script>

View File

@ -0,0 +1,34 @@
<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 text-center">
<h1>Доступ запрещен</h1>
<p>У вас нет прав на доступ к этому ресурсу. Пожалуйста, обратитесь к администратору.</p>
<router-link :to="{name: 'Home'}" class="btn btn-primary">Вернуться на главную</router-link>
</div>
</div>
</div>
</template>
<style scoped>
.container {
margin-top: 100px;
}
h1 {
font-size: 144px;
line-height: 100px;
}
h2 {
font-size: 48px;
}
p {
font-size: 24px;
}
.btn {
font-size: 24px;
}
</style>

52
front/src/pages/Login.vue Normal file
View File

@ -0,0 +1,52 @@
<template>
<div>
<h1 class="m-3 ms-sm-4">ЛОГИН</h1>
<form @submit.prevent="login" class="m-3">
<div class="mb-3">
<label for="username" class="form-label">Логин</label>
<input type="text" class="form-control" id="username" v-model="username" required autofocus>
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" class="form-control" id="password" v-model="password" required>
</div>
<button type="submit" class="btn btn-primary">ВОЙТИ</button>
</form>
</div>
</template>
<script>
import {setToken} from "../services/userService";
export default {
data() {
return {
username: '',
password: '',
}
},
methods: {
async login() {
const userData = {
username: this.username,
password: this.password
};
fetch('http://localhost:8080/api/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
})
.then(response => response.json())
.then(data => {
setToken(data.token);
this.$router.push('/');
})
.catch(error => {
console.log(error);
});
}
}
}
</script>

View File

@ -1,21 +1,27 @@
import {createRouter, createWebHistory} from 'vue-router'
import Home from './pages/Home.vue'
import Film from './pages/Film.vue'
import {createRouter, createWebHistory} from 'vue-router'
import CountriesManager from "./pages/CountriesManager.vue";
import GenresManager from "./pages/GenresManager.vue";
import TeamPositionsManager from "./pages/TeamPositionsManager.vue";
import MembersManager from "./pages/MembersManager.vue";
import FilmsManager from "./pages/FilmsManager.vue";
import Login from './pages/Login.vue';
import axios from 'axios';
import Error401 from './pages/Error401.vue';
import Account from './pages/Account.vue';
const routes = [
{ path: "/", component: Home, name: "Home" },
{ path: "/films/:id", component: Film, name: "Film" },
{ path: "/countries/manager", component: CountriesManager, name: "CountriesManager" },
{ path: "/genres/manager", component: GenresManager, name: "GenresManager" },
{ path: "/team_positions/manager", component: TeamPositionsManager, name: "TeamPositionsManager" },
{ path: "/members/manager", component: MembersManager, name: "MembersManager" },
{ path: "/films/manager", component: FilmsManager, name: "FilmManager" }
{ path: "/", component: Home, name: "Home", meta: { requiresAuth: true } },
{ path: "/films/:id", component: Film, name: "Film", meta: { requiresAuth: true } },
{ path: "/countries/manager", component: CountriesManager, name: "CountriesManager", meta: { requiresAuth: true, requiresAdmin: true } },
{ path: "/genres/manager", component: GenresManager, name: "GenresManager", meta: { requiresAuth: true, requiresAdmin: true } },
{ path: "/team_positions/manager", component: TeamPositionsManager, name: "TeamPositionsManager", meta: { requiresAuth: true, requiresAdmin: true } },
{ path: "/members/manager", component: MembersManager, name: "MembersManager", meta: { requiresAuth: true, requiresAdmin: true } },
{ path: "/films/manager", component: FilmsManager, name: "FilmManager", meta: { requiresAuth: true, requiresAdmin: true } },
{ path: "/login", component: Login, name: "Login", meta: { requiresAuth: false } },
{ path: "/account", component: Account, name: "Account", meta: { requiresAuth: true } },
{ path: "/401", component: Error401, name: "401", meta: { requiresAuth: false } }
]
const router = new createRouter({
@ -23,4 +29,14 @@ const router = new createRouter({
routes: routes
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !axios.defaults.headers.common["Authorization"]) {
next({ name: 'Login' })
} else if (to.meta.requiresAdmin && !sessionStorage.getItem("roles").includes("ROLE_ADMIN")) {
next({ name: '401' })
} else {
next()
}
})
export default router

View File

@ -0,0 +1,6 @@
import {jwtDecode} from "jwt-decode";
export function getRolesFromToken(token) {
const decodedToken = jwtDecode(token);
const rolesClaim = decodedToken["roles"];
return rolesClaim.split(" ");
}

View File

@ -0,0 +1,16 @@
import {getRolesFromToken} from '../services/JwtService';
import axios from 'axios';
export function setToken(token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
sessionStorage.setItem("roles", getRolesFromToken(token));
}
export function removeToken() {
axios.defaults.headers.common["Authorization"] = null;
sessionStorage.removeItem("roles");
}
export function isAuthorized() {
return axios.defaults.headers.common["Authorization"] != null;
}

View File

@ -3,6 +3,7 @@ package com.ip.lab.DataBase.Country.Controller;
import com.ip.lab.DataBase.Country.Service.CountryService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import java.util.List;

View File

@ -2,6 +2,8 @@ package com.ip.lab.DataBase.Country.Controller;
import com.ip.lab.DataBase.Country.Service.CountryService;
import jakarta.validation.Valid;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@ -10,6 +12,7 @@ import java.util.List;
@Controller
@RequestMapping("/countries")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class CountryMVCController {
private final CountryService service;
@ -26,6 +29,7 @@ public class CountryMVCController {
.toList());
return "countries/index";
}
@GetMapping("/{id}/edit")
public String editCountry(@PathVariable Long id, Model model) {
model.addAttribute("country", new CountryDTO(service.get(id)));

View File

@ -10,6 +10,7 @@ import com.ip.lab.DataBase.Member.Controller.MemberDTO;
import com.ip.lab.DataBase.Member.Model.Member;
import com.ip.lab.DataBase.TeamPosition.Controller.TeamPositionDTO;
import jakarta.validation.Valid;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import java.util.List;

View File

@ -13,6 +13,7 @@ import com.ip.lab.DataBase.Member.Service.MemberService;
import com.ip.lab.DataBase.TeamPosition.Controller.TeamPositionDTO;
import com.ip.lab.DataBase.TeamPosition.Service.TeamPositionService;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@ -22,6 +23,7 @@ import java.util.List;
@Controller
@RequestMapping("/films")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class FilmMVCController {
final FilmService service;
final FilmMemberTeamPosService fmtpService;
@ -42,6 +44,7 @@ public class FilmMVCController {
}
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('ROLE_USER') || hasAuthority('ROLE_USER')")
public String getFilmById(@PathVariable Long id, Model model) {
model.addAttribute("film", new FilmDTO(service.get(id)));
model.addAttribute("teamMembers",

View File

@ -3,6 +3,7 @@ package com.ip.lab.DataBase.Genre.Controller;
import com.ip.lab.DataBase.Country.Controller.CountryDTO;
import com.ip.lab.DataBase.Genre.Service.GenreService;
import jakarta.validation.Valid;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import java.util.List;

View File

@ -2,6 +2,7 @@ package com.ip.lab.DataBase.Genre.Controller;
import com.ip.lab.DataBase.Genre.Service.GenreService;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@ -10,6 +11,7 @@ import java.util.List;
@Controller
@RequestMapping("/genres")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class GenreMVCController {
final GenreService service;

View File

@ -0,0 +1,61 @@
package com.ip.lab.DataBase.Jwt;
import com.ip.lab.DataBase.User.Model.User;
import com.ip.lab.DataBase.User.Service.UserService;
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.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired
JwtService jwtService;
@Autowired
UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
// Валидируем токен
if(StringUtils.hasText(token) && jwtService.validateToken(token)){
String username = jwtService.getUsername(token);
User user = (User)userService.loadUserByUsername(username);
var authenticationToken = new UsernamePasswordAuthenticationToken(
user,
null,
user.getAuthorities()
);
var details = new WebAuthenticationDetailsSource().buildDetails(request);
authenticationToken.setDetails(details);
SecurityContextHolder
.getContext()
.setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader("Authorization");
if(!StringUtils.hasText(bearerToken) || !bearerToken.startsWith("Bearer ")){
return null;
}
return bearerToken.substring(7, bearerToken.length());
}
}

View File

@ -0,0 +1,19 @@
package com.ip.lab.DataBase.Jwt;
public class JwtAuthResponse {
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
private String token;
public JwtAuthResponse() {}
public JwtAuthResponse(String token) {
this.token = token;
}
}

View File

@ -0,0 +1,21 @@
package com.ip.lab.DataBase.Jwt;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}

View File

@ -0,0 +1,59 @@
package com.ip.lab.DataBase.Jwt;
import com.ip.lab.DataBase.Role.Model.Role;
import com.ip.lab.DataBase.User.Model.User;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
public String generateToken(User user) {
var key = getKey();
// 10 часов
Date expireDate = new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10);
var badToken = Jwts.builder()
.subject(user.getUsername())
.expiration(expireDate)
.issuedAt(new Date())
.claim("roles", user.getStringRoles())
.signWith(key);
var token = badToken.compact();
return token;
}
private SecretKey getKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(getKey())
.build()
.parse(token);
return true;
} catch (Exception e) {
return false;
}
}
public String getUsername(String token) {
return Jwts.parser()
.verifyWith(getKey())
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}
}

View File

@ -0,0 +1,24 @@
package com.ip.lab.DataBase.Main.Controller;
import com.ip.lab.DataBase.Jwt.JwtAuthResponse;
import com.ip.lab.DataBase.Main.Service.AuthenticationService;
import com.ip.lab.DataBase.User.Controller.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@RequestMapping("/api/auth")
public class AuthenticationController {
@Autowired
AuthenticationService authService;
@PostMapping
public JwtAuthResponse login(@RequestBody UserDTO user) {
String token = authService.login(user.toUser());
return new JwtAuthResponse(token);
}
}

View File

@ -0,0 +1,28 @@
package com.ip.lab.DataBase.Main.Service;
import com.ip.lab.DataBase.Jwt.JwtService;
import com.ip.lab.DataBase.User.Model.User;
import com.ip.lab.DataBase.User.Service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class AuthenticationService {
@Autowired
JwtService jwtService;
@Autowired
UserService userService;
public String login(User user) {
var fullDataUser = (User)userService.loadUserByUsername(user.getUsername());
return jwtService.generateToken(fullDataUser);
}
}

View File

@ -3,6 +3,7 @@ package com.ip.lab.DataBase.Member.Controller;
import com.ip.lab.DataBase.Country.Controller.CountryDTO;
import com.ip.lab.DataBase.Member.Service.MemberService;
import jakarta.validation.Valid;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import java.util.List;

View File

@ -2,6 +2,7 @@ package com.ip.lab.DataBase.Member.Controller;
import com.ip.lab.DataBase.Member.Service.MemberService;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@ -10,6 +11,7 @@ import java.util.List;
@Controller
@RequestMapping("/members")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class MemberMVCController {
final MemberService service;

View File

@ -0,0 +1,41 @@
package com.ip.lab.DataBase.Role.Controller;
import com.ip.lab.DataBase.Role.Service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/roles")
public class RoleController {
@Autowired
RoleService roleService;
@GetMapping("/{id}")
public RoleDTO getRoleById(@PathVariable Long id) {
return new RoleDTO(roleService.get(id));
}
@GetMapping
public List<RoleDTO> getAllRoles() {
return roleService.getAll().stream().map(RoleDTO::new).toList();
}
@PostMapping
public RoleDTO createRole(@RequestBody RoleDTO roleDTO) {
return new RoleDTO(roleService.add(roleDTO.toRole()));
}
@PutMapping
public RoleDTO updateRole(@RequestBody RoleDTO roleDTO) {
return new RoleDTO(roleService.update(roleDTO.getId(), roleDTO.toRole()));
}
@DeleteMapping("/{id}")
public RoleDTO deleteRole(@PathVariable Long id) {
return new RoleDTO(roleService.remove(id));
}
}

View File

@ -0,0 +1,38 @@
package com.ip.lab.DataBase.Role.Controller;
import com.ip.lab.DataBase.Role.Model.Role;
public class RoleDTO {
private Long id;
private String name;
public RoleDTO() {}
public RoleDTO(Role role) {
this.id = role.getId();
this.name = role.getName();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role toRole() {
Role role = new Role();
role.setId(id);
role.setName(name);
return role;
}
}

View File

@ -0,0 +1,51 @@
package com.ip.lab.DataBase.Role.Model;
import com.ip.lab.DataBase.User.Model.User;
import jakarta.persistence.*;
import org.springframework.security.core.GrantedAuthority;
import java.util.List;
@Entity
public class Role implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private List<User> users;
public Role() {}
public Role(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
@Override
public String getAuthority() {
return getName();
}
}

View File

@ -0,0 +1,7 @@
package com.ip.lab.DataBase.Role.Repository;
import com.ip.lab.DataBase.Role.Model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IRoleRepository extends JpaRepository<Role, Long> {
}

View File

@ -0,0 +1,56 @@
package com.ip.lab.DataBase.Role.Service;
import com.ip.lab.DataBase.Interfaces.ICRUDService;
import com.ip.lab.DataBase.Role.Model.Role;
import com.ip.lab.DataBase.Role.Repository.IRoleRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RoleService implements ICRUDService<Role> {
final IRoleRepository repository;
public RoleService(IRoleRepository repository) {
this.repository = repository;
}
@Override
public Role get(Long id) {
return repository.findById(id).orElse(null);
}
@Override
public List<Role> getAll() {
return repository.findAll();
}
@Override
public Role remove(Long id) {
Role role = get(id);
if (role != null) {
repository.deleteById(id);
}
return role;
}
@Override
public void removeAll() {
repository.deleteAll();
}
@Override
public Role add(Role entity) {
return repository.save(entity);
}
@Override
public Role update(Long id, Role newEntity) {
Role role = get(id);
if (role != null) {
role.setName(newEntity.getName());
return repository.save(role);
}
return null;
}
}

View File

@ -3,6 +3,7 @@ package com.ip.lab.DataBase.TeamPosition.Controller;
import com.ip.lab.DataBase.Country.Controller.CountryDTO;
import com.ip.lab.DataBase.TeamPosition.Service.TeamPositionService;
import jakarta.validation.Valid;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import java.util.List;

View File

@ -2,6 +2,7 @@ package com.ip.lab.DataBase.TeamPosition.Controller;
import com.ip.lab.DataBase.TeamPosition.Service.TeamPositionService;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@ -10,6 +11,7 @@ import java.util.List;
@Controller
@RequestMapping("/team-positions")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class TeamPositionMVCController {
final TeamPositionService service;

View File

@ -0,0 +1,39 @@
package com.ip.lab.DataBase.User.Controller;
import com.ip.lab.DataBase.User.Service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/{id}")
public UserDTO getUserById(@PathVariable Long id) {
return new UserDTO(userService.get(id));
}
@GetMapping
public List<UserDTO> getAllUsers() {
return userService.getAll().stream().map(UserDTO::new).toList();
}
@PostMapping
public UserDTO createUser(@RequestBody UserDTO userDTO) {
return new UserDTO(userService.add(userDTO.toUser()));
}
@PutMapping
public UserDTO updateUser(@RequestBody UserDTO userDTO) {
return new UserDTO(userService.update(userDTO.getId(), userDTO.toUser()));
}
@DeleteMapping("/{id}")
public UserDTO deleteUser(@PathVariable Long id) {
return new UserDTO(userService.remove(id));
}
}

View File

@ -0,0 +1,72 @@
package com.ip.lab.DataBase.User.Controller;
import java.util.ArrayList;
import java.util.List;
import com.ip.lab.DataBase.Role.Controller.RoleDTO;
import com.ip.lab.DataBase.User.Model.User;
public class UserDTO {
private Long id;
private String username;
private String password;
private List<RoleDTO> roles;
public UserDTO() {}
public UserDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.password = user.getPassword();
this.roles = user.getRoles().stream().map(RoleDTO::new).toList();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<RoleDTO> getRoles() {
return roles;
}
public void setRoles(List<RoleDTO> roles) {
this.roles = new ArrayList<>(roles);
}
public User toUser() {
User user = new User();
if (id != null) {
user.setId(id);
}
if (username != null) {
user.setUsername(username);
}
if (password != null) {
user.setPassword(password);
}
if (roles != null) {
user.setRoles(roles.stream().map(RoleDTO::toRole).toList());
}
return user;
}
}

View File

@ -0,0 +1,33 @@
package com.ip.lab.DataBase.User.Controller;
import com.ip.lab.DataBase.User.Service.UserService;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserMVCController {
final UserService userService;
public UserMVCController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public String getUser(Model model, Long id) {
model.addAttribute("user", new UserDTO(userService.get(id)));
return "users/index";
}
@PostMapping
public String createUser(@ModelAttribute @Valid UserDTO userDTO, Model model) {
userService.add(userDTO.toUser());
return "redirect:/user";
}
}

View File

@ -0,0 +1,90 @@
package com.ip.lab.DataBase.User.Model;
import com.ip.lab.DataBase.Role.Model.Role;
import jakarta.persistence.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Entity
@Table(name = "t_users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
private List<Role> roles;
public User() {}
public User(String password, String username) {
this.password = password;
this.username = username;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@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<? extends GrantedAuthority> getAuthorities() {
return getRoles();
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getStringRoles() {
return roles.stream().map(Role::getName).collect(Collectors.joining(" "));
}
}

View File

@ -0,0 +1,9 @@
package com.ip.lab.DataBase.User.Repository;
import com.ip.lab.DataBase.User.Model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
public interface IUserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}

View File

@ -0,0 +1,88 @@
package com.ip.lab.DataBase.User.Service;
import com.ip.lab.DataBase.Interfaces.ICRUDService;
import com.ip.lab.DataBase.Role.Model.Role;
import com.ip.lab.DataBase.User.Controller.UserDTO;
import com.ip.lab.DataBase.User.Model.User;
import com.ip.lab.DataBase.User.Repository.IUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Service
public class UserService implements UserDetailsService {
@Autowired
IUserRepository repository;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
public User get(Long id) {
return repository.findById(id).orElse(null);
}
public List<User> getAll() {
return repository.findAll();
}
public User remove(Long id) {
User user = get(id);
if (user != null) {
repository.delete(user);
}
return user;
}
public void removeAll() {
repository.deleteAll();
}
public User add(User entity) {
User userFromDB = repository.findByUsername(entity.getUsername());
if (userFromDB != null) {
return null;
}
if (entity.getRoles().isEmpty()) {
var role = new Role("ROLE_USER");
entity.setRoles(new ArrayList<>(Collections.singleton(role)));
}
entity.setPassword(bCryptPasswordEncoder.encode(entity.getPassword()));
return repository.save(entity);
}
public User update(Long id, User newEntity) {
User user = get(id);
if (user != null) {
user.setUsername(newEntity.getUsername());
user.setPassword(newEntity.getPassword());
user.setRoles(newEntity.getRoles());
return repository.save(user);
}
return null;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = repository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return user;
}
}

View File

@ -6,6 +6,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");

View File

@ -0,0 +1,16 @@
package com.ip.lab;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMVCConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}

View File

@ -0,0 +1,79 @@
package com.ip.lab;
import com.ip.lab.DataBase.Jwt.JwtAuthFilter;
import com.ip.lab.DataBase.Jwt.JwtAuthenticationEntryPoint;
import com.ip.lab.DataBase.User.Service.UserService;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Autowired;
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.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.server.SecurityWebFilterChain;
@EnableWebSecurity
@Configuration
@EnableMethodSecurity
public class WebSecurityConfig {
@Autowired
JwtAuthenticationEntryPoint authenticationEntryPoint;
@Bean
public JwtAuthFilter jwtAuthFilter() {
return new JwtAuthFilter();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return new UserService();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/swagger-ui/**").permitAll()
.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/auth").permitAll()
.requestMatchers("/api/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
http.formLogin()
.loginPage("/login")
.permitAll();
http.logout().permitAll();
http.userDetailsService(userDetailsService());
http.exceptionHandling( exception -> exception
.authenticationEntryPoint(authenticationEntryPoint));
http.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/style.css", "/js/**",
"/webjars/**", "/assets/**");
}
}

View File

@ -16,4 +16,6 @@ spring:
suffix: .html
enabled: true
application:
name: kionlion
name: kionlion
jwt:
secret: cuteAndFunnyUUUUUOOOOOOOOOOOOHHHHHH

View File

@ -9,6 +9,7 @@
<body>
<div layout:fragment="content">
<h1>ФИЛЬМЫ</h1>
<br/>
<div class="container">
<div class="row row-cols-md-2">
<!--Карточки с фильмами-->

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="ru"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE"></title>
@ -9,7 +9,7 @@
<link rel="stylesheet" type="text/css" th:href="@{/style.css}">
</head>
<body>
<header>
<header class="mb-5">
<nav class="navbar nav-header">
<div class="container-fluid d-flex gap-3 align-items-center header-size">
<div>
@ -20,7 +20,7 @@
</div>
</a>
</div>
<div class="menu dropdown py-3">
<div class="menu dropdown py-3 flex-grow-1">
<a class="btn clickable-element" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
<img th:src="@{/assets/header/menu-button.png}">
</a>
@ -41,9 +41,9 @@
</nav>
</header>
<div layout:fragment="content"></div>
<main layout:fragment="content" class="d-flex justify-content-center"></main>
<footer class="d-flex flex-wrap justify-content-between mt-auto">
<footer class="d-flex flex-wrap justify-content-between mt-5">
<nav class="footer-text">
<a href="/about">О сайте</a>
<a href="/our-partners">Наши партнеры</a>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}">
<head>
<meta charset="UTF-8">
<title>LOGIN</title>
</head>
<body>
<div layout:fragment="content">
<form th:action="@{/login}" method="post">
<div th:if="${param.error}" class="alert alert-error">
Неверный логин или пароль
</div>
<div th:if="${param.logout}" class="alert alert-success">
Вы вышли
</div>
<!-- Username input -->
<div data-mdb-input-init class="form-outline mb-4">
<input type="text" id="username-input" class="form-control" name="username" required autofocus />
<label class="form-label" for="username-input">Username</label>
</div>
<!-- Password input -->
<div data-mdb-input-init class="form-outline mb-4">
<input type="password" id="password-input" class="form-control" name="password" required autofocus />
<label class="form-label" for="password-input">Password</label>
</div>
<!-- Submit button -->
<button type="submit" data-mdb-button-init data-mdb-ripple-init class="btn btn-primary btn-block mb-4">Sign in</button>
</form>
</div>
</body>
</html>