From c3cfcecc0b7374bea55400e552ca3e59f38c2084 Mon Sep 17 00:00:00 2001
From: AnnZhimol <ankavanka731@gmail.com>
Date: Sat, 13 May 2023 14:12:19 +0400
Subject: [PATCH] lab 6 complete

---
 build.gradle                                  |  6 ++
 front/package-lock.json                       | 56 +++++++++--
 front/package.json                            |  1 +
 front/src/App.js                              | 20 +++-
 front/src/components/Catalog.jsx              |  2 +-
 front/src/components/Categories.jsx           | 10 +-
 front/src/components/DrivingSchools.jsx       |  7 +-
 front/src/components/Login.css                | 21 ++++
 front/src/components/Login.jsx                | 70 ++++++++++++++
 front/src/components/Logout.jsx               | 20 ++++
 front/src/components/OneDrivingSchool.jsx     | 12 ++-
 .../src/components/ReportStudentCategory.jsx  |  7 +-
 front/src/components/Students.jsx             |  6 +-
 front/src/components/commons/Header.jsx       | 11 +++
 front/src/components/commons/ItemTable.jsx    |  9 +-
 front/src/components/commons/ItemTableSC.jsx  |  7 +-
 front/src/components/commons/Table.jsx        |  2 +-
 front/src/components/commons/TableSC.jsx      |  2 +-
 front/src/components/withAuth.js              | 24 +++++
 front/src/services/DataService.js             | 25 +++--
 .../ru/ulstu/is/cbapp/WebConfiguration.java   |  6 ++
 .../PasswordEncoderConfiguration.java         | 14 +++
 .../configuration/SecurityConfiguration.java  | 87 +++++++++++++++++
 .../cbapp/configuration/jwt/JwtException.java | 11 +++
 .../is/cbapp/configuration/jwt/JwtFilter.java | 93 ++++++++++++++++++
 .../configuration/jwt/JwtProperties.java      | 27 ++++++
 .../cbapp/configuration/jwt/JwtsProvider.java | 51 ++++++++++
 .../cbapp/user/controller/UserController.java | 26 +++++
 .../user/controller/UserMvcController.java    | 44 +++++++++
 .../controller/UserSignupMvcController.java   | 50 ++++++++++
 .../ru/ulstu/is/cbapp/user/model/User.java    | 75 +++++++++++++++
 .../ru/ulstu/is/cbapp/user/model/UserDto.java | 46 +++++++++
 .../ulstu/is/cbapp/user/model/UserRole.java   | 20 ++++
 .../is/cbapp/user/model/UserSignupDto.java    | 41 ++++++++
 .../cbapp/user/repository/UserRepository.java |  8 ++
 .../user/service/UserNotFoundException.java   |  7 ++
 .../is/cbapp/user/service/UserService.java    | 96 +++++++++++++++++++
 .../is/cbapp/util/error/AdviceController.java | 45 +++++++++
 .../util/validation/ValidationException.java  | 13 +++
 .../cbapp/util/validation/ValidatorUtil.java  | 27 ++++++
 src/main/resources/application.properties     |  3 +
 src/main/resources/static/styles/login.css    | 21 ++++
 src/main/resources/templates/category.html    |  6 +-
 src/main/resources/templates/default.html     |  2 +
 .../templates/drivingSchool-one.html          |  6 +-
 .../resources/templates/drivingSchool.html    |  9 +-
 src/main/resources/templates/index.html       |  3 +-
 src/main/resources/templates/login.html       | 43 +++++++++
 src/main/resources/templates/signup.html      | 28 ++++++
 src/main/resources/templates/student.html     |  6 +-
 src/main/resources/templates/users.html       | 37 +++++++
 51 files changed, 1211 insertions(+), 58 deletions(-)
 create mode 100644 front/src/components/Login.css
 create mode 100644 front/src/components/Login.jsx
 create mode 100644 front/src/components/Logout.jsx
 create mode 100644 front/src/components/withAuth.js
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/configuration/PasswordEncoderConfiguration.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/configuration/SecurityConfiguration.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtException.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtFilter.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtProperties.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtsProvider.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/controller/UserController.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/controller/UserMvcController.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/controller/UserSignupMvcController.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/model/User.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/model/UserDto.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/model/UserRole.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/model/UserSignupDto.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/repository/UserRepository.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/service/UserNotFoundException.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/user/service/UserService.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/util/error/AdviceController.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/util/validation/ValidationException.java
 create mode 100644 src/main/java/ru/ulstu/is/cbapp/util/validation/ValidatorUtil.java
 create mode 100644 src/main/resources/static/styles/login.css
 create mode 100644 src/main/resources/templates/login.html
 create mode 100644 src/main/resources/templates/signup.html
 create mode 100644 src/main/resources/templates/users.html

diff --git a/build.gradle b/build.gradle
index 92f3afb..32a9205 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,6 +25,12 @@ dependencies {
 	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 	implementation 'com.h2database:h2:2.1.210'
 
+	implementation 'org.springframework.boot:spring-boot-starter-security'
+	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
+	implementation 'com.auth0:java-jwt:4.4.0'
+	implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
+	implementation 'javax.xml.bind:jaxb-api:2.3.1'
+
 	implementation 'org.hibernate.validator:hibernate-validator'
 
 	implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
diff --git a/front/package-lock.json b/front/package-lock.json
index fa02810..5bdfd7c 100644
--- a/front/package-lock.json
+++ b/front/package-lock.json
@@ -14,6 +14,7 @@
         "react": "^18.2.0",
         "react-bootstrap": "^2.7.2",
         "react-dom": "^18.2.0",
+        "react-router": "^6.10.0",
         "react-router-dom": "^6.6.1",
         "react-scripts": "5.0.1",
         "web-vitals": "^2.1.4"
@@ -15157,11 +15158,11 @@
       }
     },
     "node_modules/react-router": {
-      "version": "6.8.1",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
-      "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
+      "version": "6.11.1",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.1.tgz",
+      "integrity": "sha512-OZINSdjJ2WgvAi7hgNLazrEV8SGn6xrKA+MkJe9wVDMZ3zQ6fdJocUjpCUCI0cNrelWjcvon0S/QK/j0NzL3KA==",
       "dependencies": {
-        "@remix-run/router": "1.3.2"
+        "@remix-run/router": "1.6.1"
       },
       "engines": {
         "node": ">=14"
@@ -15186,6 +15187,28 @@
         "react-dom": ">=16.8"
       }
     },
+    "node_modules/react-router-dom/node_modules/react-router": {
+      "version": "6.8.1",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
+      "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
+      "dependencies": {
+        "@remix-run/router": "1.3.2"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "react": ">=16.8"
+      }
+    },
+    "node_modules/react-router/node_modules/@remix-run/router": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.1.tgz",
+      "integrity": "sha512-YUkWj+xs0oOzBe74OgErsuR3wVn+efrFhXBWrit50kOiED+pvQe2r6MWY0iJMQU/mSVKxvNzL4ZaYvjdX+G7ZA==",
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/react-scripts": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -28839,11 +28862,18 @@
       "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ=="
     },
     "react-router": {
-      "version": "6.8.1",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
-      "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
+      "version": "6.11.1",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.1.tgz",
+      "integrity": "sha512-OZINSdjJ2WgvAi7hgNLazrEV8SGn6xrKA+MkJe9wVDMZ3zQ6fdJocUjpCUCI0cNrelWjcvon0S/QK/j0NzL3KA==",
       "requires": {
-        "@remix-run/router": "1.3.2"
+        "@remix-run/router": "1.6.1"
+      },
+      "dependencies": {
+        "@remix-run/router": {
+          "version": "1.6.1",
+          "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.1.tgz",
+          "integrity": "sha512-YUkWj+xs0oOzBe74OgErsuR3wVn+efrFhXBWrit50kOiED+pvQe2r6MWY0iJMQU/mSVKxvNzL4ZaYvjdX+G7ZA=="
+        }
       }
     },
     "react-router-dom": {
@@ -28853,6 +28883,16 @@
       "requires": {
         "@remix-run/router": "1.3.2",
         "react-router": "6.8.1"
+      },
+      "dependencies": {
+        "react-router": {
+          "version": "6.8.1",
+          "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
+          "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
+          "requires": {
+            "@remix-run/router": "1.3.2"
+          }
+        }
       }
     },
     "react-scripts": {
diff --git a/front/package.json b/front/package.json
index e662b43..03344ec 100644
--- a/front/package.json
+++ b/front/package.json
@@ -9,6 +9,7 @@
     "react": "^18.2.0",
     "react-bootstrap": "^2.7.2",
     "react-dom": "^18.2.0",
+    "react-router": "^6.10.0",
     "react-router-dom": "^6.6.1",
     "react-scripts": "5.0.1",
     "web-vitals": "^2.1.4"
diff --git a/front/src/App.js b/front/src/App.js
index 28a7eab..9a54f90 100644
--- a/front/src/App.js
+++ b/front/src/App.js
@@ -7,6 +7,9 @@ import DrivingSchools from './components/DrivingSchools.jsx';
 import Categories from './components/Categories.jsx';
 import OneDrivingSchool from './components/OneDrivingSchool.jsx';
 import CountStudInCategory from './components/CountStudInCategory.jsx';
+import Login from './components/Login.jsx';
+import Logout from './components/Logout.jsx';
+import { useState, useEffect } from 'react';
 
 function Router(props) {
     return useRoutes(props.rootRoute);
@@ -21,15 +24,30 @@ function Router(props) {
       { path: '/categories', element: <Categories />, label: 'Категории' },
       { path: '/studcategory', element: <CountStudInCategory />, label: 'Количество студентов в категории' },
       { path: '/drivingSchool/:id', element: <OneDrivingSchool />},
+      { path: '/login', element: <Login />},
+      { path: '/logout', element: <Logout />},
     ];
     const links = routes.filter(route => route.hasOwnProperty('label'));
+    const [token, setToken] = useState(localStorage.getItem('token'));
+    useEffect(() => {
+
+      function handleStorageChange() {
+        setToken(localStorage.getItem('token'));
+      }
+
+      window.addEventListener('storage', handleStorageChange);
+
+      return () => {
+        window.removeEventListener('storage', handleStorageChange);
+      };
+    }, []);
     const rootRoute = [
       { path: '/', element: render(links), children: routes }
     ];
     function render(links) {
       return (
         <div className="App">
-          <Header links={links} />         
+          <Header token={token} links={links} />
           <div className="w-100">
             <Outlet />
           </div>        
diff --git a/front/src/components/Catalog.jsx b/front/src/components/Catalog.jsx
index 6c1aec3..52e973e 100644
--- a/front/src/components/Catalog.jsx
+++ b/front/src/components/Catalog.jsx
@@ -36,7 +36,7 @@ export default function Catalog(props) {
     
     return  <>
             <div>{props.name}</div>
-            <Button variant="success" onClick={handleAdd}>Добавить</Button>
+        {localStorage.getItem("role") === "ADMIN" && <Button variant="success" onClick={handleAdd}>Добавить</Button>}
             <Table 
             headers={props.headers}
             items={props.items}
diff --git a/front/src/components/Categories.jsx b/front/src/components/Categories.jsx
index 57e701e..fca1ba1 100644
--- a/front/src/components/Categories.jsx
+++ b/front/src/components/Categories.jsx
@@ -4,9 +4,9 @@ import Button from 'react-bootstrap/Button';
 import { useState, useEffect } from 'react';
 import DataService from '../services/DataService';
 import CatalogSC from "./CatalogSC.jsx";
+import withAuth from './withAuth';
 
-
-export default function Categories(props) {
+function Categories(props) {
     const headers = [
         {name: 'name', label: "Название"},         
     ];
@@ -102,7 +102,9 @@ export default function Categories(props) {
                     onDelete={handleDelete}
                     onClose={reset}
                     onBtnAdd={reset}
-                    form={form}>
+                    form={form}
+                    role={props.role}>
         </CatalogSC>
     </div>
-}
\ No newline at end of file
+}
+export default withAuth(Categories);
\ No newline at end of file
diff --git a/front/src/components/DrivingSchools.jsx b/front/src/components/DrivingSchools.jsx
index 7f662fc..0ff9d54 100644
--- a/front/src/components/DrivingSchools.jsx
+++ b/front/src/components/DrivingSchools.jsx
@@ -6,9 +6,9 @@ import Catalog from "./Catalog.jsx";
 import DrivingSchool from "../models/DrivingSchool";
 import ModalForm from './commons/ModalForm';
 import { useNavigate } from "react-router-dom";
+import withAuth from './withAuth';
 
-
-export default function DrivingSchools(props) {
+function DrivingSchools(props) {
     const headers = [
         {name: 'name', label: "Название"}, 
         {name: 'countStudents', label: "Студенты"},
@@ -151,4 +151,5 @@ export default function DrivingSchools(props) {
     </div>
 
 
-}
\ No newline at end of file
+}
+export default withAuth(DrivingSchools);
\ No newline at end of file
diff --git a/front/src/components/Login.css b/front/src/components/Login.css
new file mode 100644
index 0000000..7b886ac
--- /dev/null
+++ b/front/src/components/Login.css
@@ -0,0 +1,21 @@
+body {
+    background-color: #f8f9fa;
+}
+
+.card {
+    margin-bottom: 20px;
+}
+
+.card-title {
+    margin-bottom: 10px;
+}
+
+.btn-primary {
+    background-color: #007bff;
+    border-color: #007bff;
+}
+
+.btn-primary:hover {
+    background-color: #0069d9;
+    border-color: #0062cc;
+}
\ No newline at end of file
diff --git a/front/src/components/Login.jsx b/front/src/components/Login.jsx
new file mode 100644
index 0000000..38d55cf
--- /dev/null
+++ b/front/src/components/Login.jsx
@@ -0,0 +1,70 @@
+import React, { useState } from 'react';
+import './Login.css';
+import 'bootstrap/dist/css/bootstrap.min.css';
+import { useNavigate} from "react-router-dom";
+
+export default function Login(props) {
+    const navigate = useNavigate();
+    const [loginData, setLoginData] = useState({ login: '', password: '' });
+    const hostURL = 'http://localhost:8080';
+    const handleLoginSubmit = (e) => {
+        e.preventDefault();
+
+        login(loginData.login, loginData.password);
+    };
+
+    const login = async function (login, password) {
+        const requestParams = {
+            method: "POST",
+            headers: {
+                "Content-Type": "application/json",
+            },
+            body: JSON.stringify({login: login, password: password}),
+        };
+        const response = await fetch(hostURL + "/jwt/login", requestParams);
+        const result = await response.text();
+        if (response.status === 200) {
+            localStorage.setItem("token", result);
+            localStorage.setItem("user", login);
+            let jwtData = result.split('.')[1]
+            let decodedJwtJsonData = window.atob(jwtData);
+            let decodedJwtData = JSON.parse(decodedJwtJsonData);
+
+            let role = decodedJwtData.role;
+            localStorage.setItem("role", role.toUpperCase());
+            window.dispatchEvent(new Event("storage"));
+            navigate("/");
+        } else {
+            localStorage.removeItem("token");
+            localStorage.removeItem("user");
+            localStorage.removeItem("role");
+            alert(result);
+        }
+    }
+
+
+    return (
+        <div className="container-fluid">
+            <div className="row justify-content-center align-items-center vh-100">
+                <div className="col-sm-6 col-md-4">
+                    <div className="card">
+                        <div className="card-body">
+                            <h5 className="card-title">Авторизация</h5>
+                            <form onSubmit={handleLoginSubmit}>
+                                <div className="form-group mb-3">
+                                    <label htmlFor="login">Логин</label>
+                                    <input type="text" className="form-control" id="login" value={loginData.login} onChange={(e) => setLoginData({ ...loginData, login: e.target.value })} />
+                                </div>
+                                <div className="form-group mb-3">
+                                    <label htmlFor="loginPassword">Пароль</label>
+                                    <input type="password" className="form-control" id="loginPassword" value={loginData.password} onChange={(e) => setLoginData({ ...loginData, password: e.target.value })} />
+                                </div>
+                                <button type="submit" className="btn btn-primary">Вход</button>
+                            </form>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/front/src/components/Logout.jsx b/front/src/components/Logout.jsx
new file mode 100644
index 0000000..2c8c451
--- /dev/null
+++ b/front/src/components/Logout.jsx
@@ -0,0 +1,20 @@
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+function Logout() {
+    const navigate = useNavigate();
+
+    useEffect(() => {
+        // Удаление токена из localStorage или другого места
+        localStorage.removeItem('token');
+        localStorage.removeItem("user");
+        localStorage.removeItem("role");
+        window.dispatchEvent(new Event("storage"));
+        // Перенаправление пользователя на страницу входа или другую страницу
+        navigate('/login');
+    }, [navigate]);
+
+    return null;
+}
+
+export default Logout;
\ No newline at end of file
diff --git a/front/src/components/OneDrivingSchool.jsx b/front/src/components/OneDrivingSchool.jsx
index 72f2826..3a2f44f 100644
--- a/front/src/components/OneDrivingSchool.jsx
+++ b/front/src/components/OneDrivingSchool.jsx
@@ -8,8 +8,9 @@ import Form from 'react-bootstrap/Form';
 import Button from 'react-bootstrap/Button';
 import ModalForm from './commons/ModalForm';
 import styles from "./OneDrivingSchool.module.css";
+import withAuth from './withAuth';
 
-export default function OneDrivingSchool(props) {
+function OneDrivingSchool(props) {
     const { id } = useParams();
 
     const url = '/drivingSchool/id';
@@ -225,9 +226,9 @@ export default function OneDrivingSchool(props) {
         </Link>
         <h1>Название: {drivingSchool.name}</h1>
         <h2>Количество студентов: {drivingSchool.countStudents}</h2>
-        <Button name="Зачисление" onClick={showModalFormHire} variant="btn btn-outline-success">Зачислить студента</Button>
-        <Button name='Отчисление' onClick={showModalFormDismiss} variant="btn btn-outline-danger">Отчислить студента</Button>
-        <Button name='Выбор категории' onClick={showModalFormChooseCategory} variant="btn btn-outline-primary">Выбор категории</Button>
+        {localStorage.getItem("role") === "ADMIN" && <Button name="Зачисление" onClick={showModalFormHire} variant="btn btn-outline-success">Зачислить студента</Button>}
+        {localStorage.getItem("role") === "ADMIN" && <Button name='Отчисление' onClick={showModalFormDismiss} variant="btn btn-outline-danger">Отчислить студента</Button>}
+        {localStorage.getItem("role") === "ADMIN" && <Button name='Выбор категории' onClick={showModalFormChooseCategory} variant="btn btn-outline-primary">Выбор категории</Button>}
         <div >
             <table className={`table table-hover`}>
             <thead>
@@ -253,4 +254,5 @@ export default function OneDrivingSchool(props) {
         <ModalForm show={isShowChooseCategory} onClose={unshowModalFormChooseCategory} modalTitle={"Управление категориями"} form={formCheckBoxesCategory}></ModalForm>
   </div>
 
-}
\ No newline at end of file
+}
+export default withAuth(OneDrivingSchool);
\ No newline at end of file
diff --git a/front/src/components/ReportStudentCategory.jsx b/front/src/components/ReportStudentCategory.jsx
index d496dc6..c46ab31 100644
--- a/front/src/components/ReportStudentCategory.jsx
+++ b/front/src/components/ReportStudentCategory.jsx
@@ -6,7 +6,8 @@ import Form from 'react-bootstrap/Form';
 import Button from 'react-bootstrap/Button';
 import DrivingSchool from '../models/DrivingSchool';
 import { Link} from "react-router-dom";
-export default function ReportStudentCategory(props) {
+import withAuth from './withAuth';
+function ReportStudentCategory(props) {
     const headersEmp = [
         {name: 'surname', label: "Фамилия"},
         {name: 'name', label: "Имя"},
@@ -69,4 +70,6 @@ export default function ReportStudentCategory(props) {
             </table>
         </div>
     </div>
-}
\ No newline at end of file
+}
+
+export default withAuth(ReportStudentCategory);
\ No newline at end of file
diff --git a/front/src/components/Students.jsx b/front/src/components/Students.jsx
index b2c36f3..d9c96ed 100644
--- a/front/src/components/Students.jsx
+++ b/front/src/components/Students.jsx
@@ -4,7 +4,8 @@ import Button from 'react-bootstrap/Button';
 import { useState, useEffect } from 'react';
 import DataService from '../services/DataService';
 import CatalogSC from "./CatalogSC.jsx";
-export default function Students(props) {
+import withAuth from './withAuth';
+function Students(props) {
     const headers = [
         {name: 'surname', label: "Фамилия"}, 
         {name: 'name', label: "Имя"}, 
@@ -112,4 +113,5 @@ export default function Students(props) {
                     form={form}>
         </CatalogSC>
     </div>
-}
\ No newline at end of file
+}
+export default withAuth(Students);
\ No newline at end of file
diff --git a/front/src/components/commons/Header.jsx b/front/src/components/commons/Header.jsx
index 479c283..c92055d 100644
--- a/front/src/components/commons/Header.jsx
+++ b/front/src/components/commons/Header.jsx
@@ -2,6 +2,8 @@ import { NavLink } from 'react-router-dom';
 import Container from 'react-bootstrap/Container';
 import Nav from 'react-bootstrap/Nav';
 import Navbar from 'react-bootstrap/Navbar';
+import styles from "./Header.module.css";
+import { useState, useEffect } from 'react';
 
 export default function Header(props) {
     return (
@@ -17,6 +19,15 @@ export default function Header(props) {
                             </NavLink>                            
                         )
                 }
+              {props.token && props.token !== 'undefined' ?
+                  <NavLink className="nav-link" to="/logout">
+                      <div>Выйти</div>
+                  </NavLink>
+                  :
+                  <NavLink className="nav-link" to="/login">
+                      <div>Войти</div>
+                  </NavLink>
+              }
           </Nav>
         </Navbar.Collapse>
       </Container>
diff --git a/front/src/components/commons/ItemTable.jsx b/front/src/components/commons/ItemTable.jsx
index 307c6f4..6b30a63 100644
--- a/front/src/components/commons/ItemTable.jsx
+++ b/front/src/components/commons/ItemTable.jsx
@@ -14,9 +14,10 @@ export default function ItemTable(props) {
             {
             props.headers.map((header) => <td key={`${header.name}_${props.item.id}`}>{props.item[header.name]}</td>)
             }
-            {props.isOnlyView || <td key={`controls_${props.item.id}`}>
-                <Button variant="btn btn-outline-warning" onClick={chooseDrivingSchool}>Выбрать</Button>
-        <Button variant="btn btn-outline-primary" onClick={edit}>Редактировать</Button>
-        <Button variant="btn btn-outline-danger" onClick={remove}>Удалить</Button></td>}
+        {localStorage.getItem("role") !== "ADMIN" ||props.isOnlyView || <td key={`controls_${props.item.id}`}>
+            {localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-warning" onClick={chooseDrivingSchool}>Выбрать</Button>}
+            {localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-primary" onClick={edit}>Редактировать</Button>}
+            {localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-danger" onClick={remove}>Удалить</Button>}
+        </td>}
          </tr>
 }
\ No newline at end of file
diff --git a/front/src/components/commons/ItemTableSC.jsx b/front/src/components/commons/ItemTableSC.jsx
index 4fec8bb..ae9627b 100644
--- a/front/src/components/commons/ItemTableSC.jsx
+++ b/front/src/components/commons/ItemTableSC.jsx
@@ -11,8 +11,9 @@ export default function ItemTableSC(props) {
         {
             props.headers.map((header) => <td key={`${header.name}_${props.item.id}`}>{props.item[header.name]}</td>)
         }
-        {props.isOnlyView || <td key={`controls_${props.item.id}`}>
-            <Button variant="btn btn-outline-primary" onClick={edit}>Редактировать</Button>
-            <Button variant="btn btn-outline-danger" onClick={remove}>Удалить</Button></td>}
+        {localStorage.getItem("role") !== "ADMIN" ||props.isOnlyView || <td key={`controls_${props.item.id}`}>
+            {localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-primary" onClick={edit}>Редактировать</Button>}
+            {localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-danger" onClick={remove}>Удалить</Button>}
+        </td>}
     </tr>
 }
\ No newline at end of file
diff --git a/front/src/components/commons/Table.jsx b/front/src/components/commons/Table.jsx
index 0b990d7..15fec78 100644
--- a/front/src/components/commons/Table.jsx
+++ b/front/src/components/commons/Table.jsx
@@ -17,7 +17,7 @@ export default function Table(props) {
                         {
                         props.headers.map((header) => <th key={header.name}>{header.label}</th>)
                         }
-                        {props.isOnlyView || <th key='controls'>Элементы управления</th>}
+                        {localStorage.getItem("role") !== "ADMIN" || props.isOnlyView || <th key='controls'>Элементы управления</th>}
                         
                     </tr>
             </thead>
diff --git a/front/src/components/commons/TableSC.jsx b/front/src/components/commons/TableSC.jsx
index 20c8127..50f9618 100644
--- a/front/src/components/commons/TableSC.jsx
+++ b/front/src/components/commons/TableSC.jsx
@@ -14,7 +14,7 @@ export default function TableSC(props) {
                 {
                     props.headers.map((header) => <th key={header.name}>{header.label}</th>)
                 }
-                {props.isOnlyView || <th key='controls'>Элементы управления</th>}
+                {localStorage.getItem("role") !== "ADMIN" || props.isOnlyView || <th key='controls'>Элементы управления</th>}
 
             </tr>
             </thead>
diff --git a/front/src/components/withAuth.js b/front/src/components/withAuth.js
new file mode 100644
index 0000000..adf1f7b
--- /dev/null
+++ b/front/src/components/withAuth.js
@@ -0,0 +1,24 @@
+import React from 'react'
+import { useNavigate} from "react-router-dom";
+import { useEffect } from 'react';
+
+const withAuth = (Component) => {
+    const AuthenticatedComponent = (props) => {
+        const navigate = useNavigate();
+        const token = localStorage.getItem('token');
+
+        useEffect(() => {
+            if (!token || token === 'undefined') {
+                navigate('/login');
+            }
+        }, [navigate, token]);
+
+        if (token && token !== 'undefined') {
+            return <Component {...props} />
+        }
+        return null;
+    }
+    return AuthenticatedComponent;
+}
+
+export default withAuth;
\ No newline at end of file
diff --git a/front/src/services/DataService.js b/front/src/services/DataService.js
index 84c3858..3918153 100644
--- a/front/src/services/DataService.js
+++ b/front/src/services/DataService.js
@@ -11,32 +11,45 @@ function toJSON(data) {
     }
     return jsonObj;
 }
+const getTokenForHeader = function () {
+    return "Bearer " + localStorage.getItem("token");
+}
 
 export default class DataService {
     static dataUrlPrefix = 'http://localhost:8080/api';
 
     static async readAll(url, transformer) {
-        const response = await axios.get(this.dataUrlPrefix + url);
+        const response = await axios.get(this.dataUrlPrefix + url, {headers: {
+                "Authorization": getTokenForHeader(),
+            }});
         return response.data.map(item => transformer(item));
     }
 
     static async read(url, transformer) {
-        const response = await axios.get(this.dataUrlPrefix + url);      
+        const response = await axios.get(this.dataUrlPrefix + url, {headers: {
+                "Authorization": getTokenForHeader(),
+            }});
         return transformer(response.data);
     }
 
     static async create(url, data) {
-        const response = await axios.post(this.dataUrlPrefix + url);
+        const response = await axios.post(this.dataUrlPrefix + url, {headers: {
+                "Authorization": getTokenForHeader(),
+            }});
         return true;
     }
 
-    static async update(url, data) {  
-        const response = await axios.put(this.dataUrlPrefix + url);
+    static async update(url, data) {
+        const response = await axios.put(this.dataUrlPrefix + url, {headers: {
+                "Authorization": getTokenForHeader(),
+            }});
         return true;
     }
 
     static async delete(url) {
-        const response = await axios.delete(this.dataUrlPrefix + url);
+        const response = await axios.delete(this.dataUrlPrefix + url, {headers: {
+                "Authorization": getTokenForHeader(),
+            }});
         return response.data.id;
     }
 }
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/WebConfiguration.java b/src/main/java/ru/ulstu/is/cbapp/WebConfiguration.java
index f5229a0..09b8171 100644
--- a/src/main/java/ru/ulstu/is/cbapp/WebConfiguration.java
+++ b/src/main/java/ru/ulstu/is/cbapp/WebConfiguration.java
@@ -8,6 +8,12 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 public class WebConfiguration implements WebMvcConfigurer {
     public static final String REST_API = "/api";
 
+    @Override
+    public void addViewControllers(ViewControllerRegistry registry) {
+        WebMvcConfigurer.super.addViewControllers(registry);
+        registry.addViewController("login");
+    }
+
     @Override
     public void addCorsMappings(CorsRegistry registry) {
         registry.addMapping("/**").allowedMethods("*");
diff --git a/src/main/java/ru/ulstu/is/cbapp/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/is/cbapp/configuration/PasswordEncoderConfiguration.java
new file mode 100644
index 0000000..d19434b
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/configuration/PasswordEncoderConfiguration.java
@@ -0,0 +1,14 @@
+package ru.ulstu.is.cbapp.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class PasswordEncoderConfiguration {
+    @Bean
+    public PasswordEncoder createPasswordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/is/cbapp/configuration/SecurityConfiguration.java
new file mode 100644
index 0000000..724f2de
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/configuration/SecurityConfiguration.java
@@ -0,0 +1,87 @@
+package ru.ulstu.is.cbapp.configuration;
+
+import ru.ulstu.is.cbapp.configuration.jwt.JwtFilter;
+import ru.ulstu.is.cbapp.user.controller.UserController;
+import ru.ulstu.is.cbapp.user.controller.UserSignupMvcController;
+import ru.ulstu.is.cbapp.user.model.UserRole;
+import ru.ulstu.is.cbapp.user.service.UserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpMethod;
+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.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity(securedEnabled = true)
+public class SecurityConfiguration {
+    private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
+    private static final String LOGIN_URL = "/login";
+    public static final String SPA_URL_MASK = "/{path:[^\\.]*}";
+    private UserService userService;
+    private JwtFilter jwtFilter;
+
+    public SecurityConfiguration(UserService userService) {
+        this.userService = userService;
+        this.jwtFilter = new JwtFilter(userService);
+        createAdminOnStartup();
+        createTestUsersOnStartup();
+    }
+
+    private void createAdminOnStartup() {
+        final String admin = "admin";
+        if (userService.findByLogin(admin) == null) {
+            log.info("Admin user successfully created");
+            userService.createUser(admin, admin, admin, UserRole.ADMIN);
+        }
+    }
+
+    private void createTestUsersOnStartup() {
+        final String[] userNames = {"user1", "user2", "user3"};
+        for (String user : userNames) {
+            if (userService.findByLogin(user) == null) {
+                log.info("User %s successfully created".formatted(user));
+                userService.createUser(user, user, user, UserRole.USER);
+            }
+        }
+    }
+
+    @Bean
+    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
+        http.cors()
+                .and()
+                .csrf().disable()
+                .authorizeHttpRequests((a) -> a
+                        .requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
+                        .requestMatchers(HttpMethod.PUT, "/api/**").hasRole("ADMIN")
+                        .requestMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN")
+                        .requestMatchers("/api/**").authenticated()
+                        .requestMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll())
+                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
+                .anonymous().and().authorizeHttpRequests((a) ->
+                        a.requestMatchers(LOGIN_URL, UserSignupMvcController.SIGNUP_URL, "/h2-console/**")
+                                .permitAll().requestMatchers("/users").hasRole("ADMIN").anyRequest().authenticated())
+                .formLogin()
+                .loginPage(LOGIN_URL).permitAll()
+                .and()
+                .logout().permitAll()
+                .logoutSuccessUrl("/login")
+                .and()
+                .userDetailsService(userService);
+
+        return http.build();
+    }
+
+    @Bean
+    public WebSecurityCustomizer webSecurityCustomizer() {
+        return (web) -> web.ignoring().requestMatchers("/css/**", "/js/**", "/templates/**", "/webjars/**", "/styles/**");    }
+}
diff --git a/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtException.java b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtException.java
new file mode 100644
index 0000000..10e4434
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtException.java
@@ -0,0 +1,11 @@
+package ru.ulstu.is.cbapp.configuration.jwt;
+
+public class JwtException extends RuntimeException {
+    public JwtException(Throwable throwable) {
+        super(throwable);
+    }
+
+    public JwtException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtFilter.java b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtFilter.java
new file mode 100644
index 0000000..8c598af
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtFilter.java
@@ -0,0 +1,93 @@
+package ru.ulstu.is.cbapp.configuration.jwt;
+
+import ru.ulstu.is.cbapp.user.service.UserService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.MediaType;
+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.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+
+import java.io.IOException;
+
+public class JwtFilter extends AbstractPreAuthenticatedProcessingFilter {
+    private static final String AUTHORIZATION = "Authorization";
+    public static final String TOKEN_BEGIN_STR = "Bearer ";
+
+    private final UserService userService;
+
+    public JwtFilter(UserService userService) {
+        this.userService = userService;
+    }
+
+    private String getTokenFromRequest(HttpServletRequest request) {
+        String bearer = request.getHeader(AUTHORIZATION);
+        if (StringUtils.hasText(bearer) && bearer.startsWith(TOKEN_BEGIN_STR)) {
+            return bearer.substring(TOKEN_BEGIN_STR.length());
+        }
+        return null;
+    }
+
+    private void raiseException(ServletResponse response, int status, String message) throws IOException {
+        if (response instanceof final HttpServletResponse httpResponse) {
+            httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
+            httpResponse.setStatus(status);
+            final byte[] body = new ObjectMapper().writeValueAsBytes(message);
+            response.getOutputStream().write(body);
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request,
+                         ServletResponse response,
+                         FilterChain chain) throws IOException, ServletException {
+        if (request instanceof final HttpServletRequest httpRequest) {
+            final String token = getTokenFromRequest(httpRequest);
+            if (StringUtils.hasText(token)) {
+                try {
+                    final UserDetails user = userService.loadUserByToken(token);
+                    final UsernamePasswordAuthenticationToken auth =
+                            new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+                    SecurityContextHolder.getContext().setAuthentication(auth);
+                } catch (JwtException e) {
+                    raiseException(response, HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
+                    return;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    raiseException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                            String.format("Internal error: %s", e.getMessage()));
+                    return;
+                }
+            }
+        }
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        if (httpRequest.getRequestURI().startsWith("/api/")) {
+            // Для URL, начинающихся с /api/, выполняем проверку наличия токена
+            super.doFilter(request, response, chain);
+        } else {
+            // Для остальных URL выполняем авторизацию
+            chain.doFilter(request, response);
+        }
+    }
+
+    @Override
+    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
+        String token = getTokenFromRequest(request);
+        // Возвращаем токен как принципала
+        return token;
+    }
+
+    @Override
+    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
+        return new WebAuthenticationDetailsSource().buildDetails(request);
+    }
+}
diff --git a/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtProperties.java b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtProperties.java
new file mode 100644
index 0000000..901fb6d
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtProperties.java
@@ -0,0 +1,27 @@
+package ru.ulstu.is.cbapp.configuration.jwt;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true)
+public class JwtProperties {
+    private String devToken = "";
+    private Boolean isDev = true;
+
+    public String getDevToken() {
+        return devToken;
+    }
+
+    public void setDevToken(String devToken) {
+        this.devToken = devToken;
+    }
+
+    public Boolean isDev() {
+        return isDev;
+    }
+
+    public void setDev(Boolean dev) {
+        isDev = dev;
+    }
+}
diff --git a/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtsProvider.java b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtsProvider.java
new file mode 100644
index 0000000..56037cf
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/configuration/jwt/JwtsProvider.java
@@ -0,0 +1,51 @@
+package ru.ulstu.is.cbapp.configuration.jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+@Component
+public class JwtsProvider {
+
+    @Value("jwt.secret")
+    private String secret;
+
+    public String generateToken(String login, String role) {
+        Date date = Date.from(LocalDate.now().plusDays(15).atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+        JwtBuilder builder = Jwts.builder()
+                .setSubject(login)
+                .setExpiration(date)
+                .signWith(SignatureAlgorithm.HS512, secret);
+        Claims claims = Jwts.claims();
+        claims.put("role", role);
+        builder.addClaims(claims);
+        return builder.compact();
+    }
+
+    public boolean validateToken(String token) {
+        try {
+            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public String getLogin(String token) {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody()
+                .getSubject();
+    }
+}
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/controller/UserController.java b/src/main/java/ru/ulstu/is/cbapp/user/controller/UserController.java
new file mode 100644
index 0000000..8f7fac7
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/controller/UserController.java
@@ -0,0 +1,26 @@
+package ru.ulstu.is.cbapp.user.controller;
+
+import ru.ulstu.is.cbapp.WebConfiguration;
+import ru.ulstu.is.cbapp.user.model.UserDto;
+import ru.ulstu.is.cbapp.user.service.UserService;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController()
+public class UserController {
+    public static final String URL_LOGIN = "/jwt/login";
+
+    private final UserService userService;
+
+    public UserController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @PostMapping(URL_LOGIN)
+    public String login(@RequestBody @Valid UserDto userDto) {
+        return userService.loginAndGetToken(userDto);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/controller/UserMvcController.java b/src/main/java/ru/ulstu/is/cbapp/user/controller/UserMvcController.java
new file mode 100644
index 0000000..fcca42e
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/controller/UserMvcController.java
@@ -0,0 +1,44 @@
+package ru.ulstu.is.cbapp.user.controller;
+
+import ru.ulstu.is.cbapp.user.model.User;
+import ru.ulstu.is.cbapp.user.model.UserDto;
+import ru.ulstu.is.cbapp.user.model.UserRole;
+import ru.ulstu.is.cbapp.user.service.UserService;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+
+import java.util.List;
+import java.util.stream.IntStream;
+
+@Controller
+@RequestMapping("/users")
+public class UserMvcController {
+    private final UserService userService;
+
+    public UserMvcController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @GetMapping
+    @Secured({UserRole.AsString.ADMIN})
+    public String getUsers(@RequestParam(defaultValue = "1") int page,
+                           @RequestParam(defaultValue = "5") int size,
+                           Model model) {
+        final Page<UserDto> users = userService.findAllPages(page, size)
+                .map(UserDto::new);
+        model.addAttribute("users", users);
+        final int totalPages = users.getTotalPages();
+        final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
+                .boxed()
+                .toList();
+        model.addAttribute("pages", pageNumbers);
+        model.addAttribute("totalPages", totalPages);
+        return "users";
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/controller/UserSignupMvcController.java b/src/main/java/ru/ulstu/is/cbapp/user/controller/UserSignupMvcController.java
new file mode 100644
index 0000000..ad8db68
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/controller/UserSignupMvcController.java
@@ -0,0 +1,50 @@
+package ru.ulstu.is.cbapp.user.controller;
+
+import ru.ulstu.is.cbapp.user.model.User;
+import ru.ulstu.is.cbapp.user.model.UserSignupDto;
+import ru.ulstu.is.cbapp.user.service.UserService;
+import ru.ulstu.is.cbapp.util.validation.ValidationException;
+import jakarta.validation.Valid;
+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 org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping(UserSignupMvcController.SIGNUP_URL)
+public class UserSignupMvcController {
+    public static final String SIGNUP_URL = "/signup";
+
+    private final UserService userService;
+
+    public UserSignupMvcController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @GetMapping
+    public String showSignupForm(Model model) {
+        model.addAttribute("userDto", new UserSignupDto());
+        return "signup";
+    }
+
+    @PostMapping
+    public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
+                         BindingResult bindingResult,
+                         Model model) {
+        if (bindingResult.hasErrors()) {
+            model.addAttribute("errors", bindingResult.getAllErrors());
+            return "signup";
+        }
+        try {
+            final User user = userService.createUser(
+                    userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
+            return "redirect:/login?created=" + user.getLogin();
+        } catch (ValidationException e) {
+            model.addAttribute("errors", e.getMessage());
+            return "signup";
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/model/User.java b/src/main/java/ru/ulstu/is/cbapp/user/model/User.java
new file mode 100644
index 0000000..9ca440c
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/model/User.java
@@ -0,0 +1,75 @@
+package ru.ulstu.is.cbapp.user.model;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+
+import java.util.Objects;
+
+@Entity
+@Table(name = "users")
+public class User {
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private Long id;
+    @Column(nullable = false, unique = true, length = 64)
+    @NotBlank
+    @Size(min = 3, max = 64)
+    private String login;
+    @Column(nullable = false, length = 64)
+    @NotBlank
+    @Size(min = 6, max = 64)
+    private String password;
+    private UserRole role;
+
+    public User() {
+    }
+
+    public User(String login, String password) {
+        this(login, password, UserRole.USER);
+    }
+
+    public User(String login, String password, UserRole role) {
+        this.login = login;
+        this.password = password;
+        this.role = role;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getLogin() {
+        return login;
+    }
+
+    public void setLogin(String login) {
+        this.login = login;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public UserRole getRole() {
+        return role;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        User user = (User) o;
+        return Objects.equals(id, user.id) && Objects.equals(login, user.login);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, login);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/model/UserDto.java b/src/main/java/ru/ulstu/is/cbapp/user/model/UserDto.java
new file mode 100644
index 0000000..fbfb259
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/model/UserDto.java
@@ -0,0 +1,46 @@
+package ru.ulstu.is.cbapp.user.model;
+
+public class UserDto {
+    private long id;
+    private String login;
+    private UserRole role;
+    private String password;
+
+    public UserDto(User user) {
+        this.id = user.getId();
+        this.login = user.getLogin();
+        this.role = user.getRole();
+        this.password = user.getPassword();
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public String getLogin() {
+        return login;
+    }
+
+    public UserRole getRole() {
+        return role;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public UserDto() {
+    }
+
+    public void setLogin(String login) {
+        this.login = login;
+    }
+
+    public void setRole(UserRole role) {
+        this.role = role;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/model/UserRole.java b/src/main/java/ru/ulstu/is/cbapp/user/model/UserRole.java
new file mode 100644
index 0000000..bc20dc7
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/model/UserRole.java
@@ -0,0 +1,20 @@
+package ru.ulstu.is.cbapp.user.model;
+
+import org.springframework.security.core.GrantedAuthority;
+
+public enum UserRole implements GrantedAuthority {
+    ADMIN,
+    USER;
+
+    private static final String PREFIX = "ROLE_";
+
+    @Override
+    public String getAuthority() {
+        return PREFIX + this.name();
+    }
+
+    public static final class AsString {
+        public static final String ADMIN = PREFIX + "ADMIN";
+        public static final String USER = PREFIX + "USER";
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/model/UserSignupDto.java b/src/main/java/ru/ulstu/is/cbapp/user/model/UserSignupDto.java
new file mode 100644
index 0000000..14080f9
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/model/UserSignupDto.java
@@ -0,0 +1,41 @@
+package ru.ulstu.is.cbapp.user.model;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+
+public class UserSignupDto {
+    @NotBlank
+    @Size(min = 3, max = 64)
+    private String login;
+    @NotBlank
+    @Size(min = 6, max = 64)
+    private String password;
+    @NotBlank
+    @Size(min = 6, max = 64)
+    private String passwordConfirm;
+
+    public String getLogin() {
+        return login;
+    }
+
+    public void setLogin(String login) {
+        this.login = login;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getPasswordConfirm() {
+        return passwordConfirm;
+    }
+
+    public void setPasswordConfirm(String passwordConfirm) {
+        this.passwordConfirm = passwordConfirm;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/repository/UserRepository.java b/src/main/java/ru/ulstu/is/cbapp/user/repository/UserRepository.java
new file mode 100644
index 0000000..cfe26fc
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/repository/UserRepository.java
@@ -0,0 +1,8 @@
+package ru.ulstu.is.cbapp.user.repository;
+
+import ru.ulstu.is.cbapp.user.model.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserRepository extends JpaRepository<User, Long> {
+    User findOneByLoginIgnoreCase(String login);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/service/UserNotFoundException.java b/src/main/java/ru/ulstu/is/cbapp/user/service/UserNotFoundException.java
new file mode 100644
index 0000000..7ceb619
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/service/UserNotFoundException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.is.cbapp.user.service;
+
+public class UserNotFoundException extends RuntimeException {
+    public UserNotFoundException(String login) {
+        super(String.format("User not found '%s'", login));
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/user/service/UserService.java b/src/main/java/ru/ulstu/is/cbapp/user/service/UserService.java
new file mode 100644
index 0000000..88efa9f
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/user/service/UserService.java
@@ -0,0 +1,96 @@
+package ru.ulstu.is.cbapp.user.service;
+
+import ru.ulstu.is.cbapp.configuration.jwt.JwtException;
+import ru.ulstu.is.cbapp.configuration.jwt.JwtsProvider;
+import ru.ulstu.is.cbapp.user.model.User;
+import ru.ulstu.is.cbapp.user.model.UserDto;
+import ru.ulstu.is.cbapp.user.model.UserRole;
+import ru.ulstu.is.cbapp.user.repository.UserRepository;
+import ru.ulstu.is.cbapp.util.validation.ValidationException;
+import ru.ulstu.is.cbapp.util.validation.ValidatorUtil;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+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.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+
+import java.util.Collections;
+import java.util.Objects;
+
+@Service
+public class UserService implements UserDetailsService {
+    private final UserRepository userRepository;
+    private final PasswordEncoder passwordEncoder;
+    private final ValidatorUtil validatorUtil;
+    private final JwtsProvider jwtProvider;
+
+    public UserService(UserRepository userRepository,
+                       PasswordEncoder passwordEncoder,
+                       ValidatorUtil validatorUtil,
+                       JwtsProvider jwtProvider) {
+        this.userRepository = userRepository;
+        this.passwordEncoder = passwordEncoder;
+        this.validatorUtil = validatorUtil;
+        this.jwtProvider = jwtProvider;
+    }
+
+    public Page<User> findAllPages(int page, int size) {
+        return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending()));
+    }
+
+    public User findByLogin(String login) {
+        return userRepository.findOneByLoginIgnoreCase(login);
+    }
+
+    public User createUser(String login, String password, String passwordConfirm) {
+        return createUser(login, password, passwordConfirm, UserRole.USER);
+    }
+
+    public User createUser(String login, String password, String passwordConfirm, UserRole role) {
+        if (findByLogin(login) != null) {
+            throw new ValidationException(String.format("User '%s' already exists", login));
+        }
+        final User user = new User(login, passwordEncoder.encode(password), role);
+        validatorUtil.validate(user);
+        if (!Objects.equals(password, passwordConfirm)) {
+            throw new ValidationException("Passwords not equals");
+        }
+        return userRepository.save(user);
+    }
+
+    public String loginAndGetToken(UserDto userDto) {
+        final User user = findByLogin(userDto.getLogin());
+        if (user == null) {
+            throw new UserNotFoundException(userDto.getLogin());
+        }
+        if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
+            throw new UserNotFoundException(user.getLogin());
+        }
+        return jwtProvider.generateToken(user.getLogin(), user.getRole().name());
+    }
+
+    public UserDetails loadUserByToken(String token) throws UsernameNotFoundException {
+        if (!jwtProvider.validateToken(token)) {
+            throw new JwtException("Bad token");
+        }
+        final String userLogin = jwtProvider.getLogin(token);
+        if (userLogin.isEmpty()) {
+            throw new JwtException("Token is not contain Login");
+        }
+        return loadUserByUsername(userLogin);
+    }
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        final User userEntity = findByLogin(username);
+        if (userEntity == null) {
+            throw new UsernameNotFoundException(username);
+        }
+        return new org.springframework.security.core.userdetails.User(
+                userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
+    }
+}
diff --git a/src/main/java/ru/ulstu/is/cbapp/util/error/AdviceController.java b/src/main/java/ru/ulstu/is/cbapp/util/error/AdviceController.java
new file mode 100644
index 0000000..edf5618
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/util/error/AdviceController.java
@@ -0,0 +1,45 @@
+package ru.ulstu.is.cbapp.util.error;
+
+import ru.ulstu.is.cbapp.util.validation.ValidationException;
+import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestController;
+import java.nio.file.AccessDeniedException;
+
+import java.util.stream.Collectors;
+
+@ControllerAdvice(annotations = RestController.class)
+public class AdviceController {
+    @ExceptionHandler({
+            ValidationException.class
+    })
+    public ResponseEntity<Object> handleException(Throwable e) {
+        return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler({
+            AccessDeniedException.class
+    })
+    public ResponseEntity<Object> handleAccessDeniedException(Throwable e) {
+        return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<Object> handleBindException(MethodArgumentNotValidException e) {
+        final ValidationException validationException = new ValidationException(
+                e.getBindingResult().getAllErrors().stream()
+                        .map(DefaultMessageSourceResolvable::getDefaultMessage)
+                        .collect(Collectors.toSet()));
+        return handleException(validationException);
+    }
+
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Object> handleUnknownException(Throwable e) {
+        e.printStackTrace();
+        return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+}
diff --git a/src/main/java/ru/ulstu/is/cbapp/util/validation/ValidationException.java b/src/main/java/ru/ulstu/is/cbapp/util/validation/ValidationException.java
new file mode 100644
index 0000000..a953b6e
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/util/validation/ValidationException.java
@@ -0,0 +1,13 @@
+package ru.ulstu.is.cbapp.util.validation;
+
+import java.util.Set;
+
+public class ValidationException extends RuntimeException {
+    public <T> ValidationException(Set<String> errors) {
+        super(String.join("\n", errors));
+    }
+
+    public <T> ValidationException(String error) {
+        super(error);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/cbapp/util/validation/ValidatorUtil.java b/src/main/java/ru/ulstu/is/cbapp/util/validation/ValidatorUtil.java
new file mode 100644
index 0000000..9c3f627
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/cbapp/util/validation/ValidatorUtil.java
@@ -0,0 +1,27 @@
+package ru.ulstu.is.cbapp.util.validation;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Component
+public class ValidatorUtil {
+    private final Validator validator;
+
+    public ValidatorUtil() {
+        this.validator = Validation.buildDefaultValidatorFactory().getValidator();
+    }
+
+    public <T> void validate(T object) {
+        final Set<ConstraintViolation<T>> errors = validator.validate(object);
+        if (!errors.isEmpty()) {
+            throw new ValidationException(errors.stream()
+                    .map(ConstraintViolation::getMessage)
+                    .collect(Collectors.toSet()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index da7b0b1..d4ce457 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -9,3 +9,6 @@ spring.jpa.hibernate.ddl-auto=update
 spring.h2.console.enabled=true
 spring.h2.console.settings.trace=false
 spring.h2.console.settings.web-allow-others=false
+jwt.dev-token=my-secret-jwt
+jwt.dev=true
+jwt.secret = my-secret-jwt
diff --git a/src/main/resources/static/styles/login.css b/src/main/resources/static/styles/login.css
new file mode 100644
index 0000000..7b886ac
--- /dev/null
+++ b/src/main/resources/static/styles/login.css
@@ -0,0 +1,21 @@
+body {
+    background-color: #f8f9fa;
+}
+
+.card {
+    margin-bottom: 20px;
+}
+
+.card-title {
+    margin-bottom: 10px;
+}
+
+.btn-primary {
+    background-color: #007bff;
+    border-color: #007bff;
+}
+
+.btn-primary:hover {
+    background-color: #0069d9;
+    border-color: #0062cc;
+}
\ No newline at end of file
diff --git a/src/main/resources/templates/category.html b/src/main/resources/templates/category.html
index a7efd99..faee842 100644
--- a/src/main/resources/templates/category.html
+++ b/src/main/resources/templates/category.html
@@ -7,7 +7,7 @@
 <body>
 <div layout:fragment="content">
   <div>Категории</div>
-  <div>
+  <div sec:authorize="hasRole('ROLE_ADMIN')">
     <a class="btn btn-success button-fixed"
        onsubmit="openModalEdit()" data-bs-target="#categoryEditModal" data-bs-toggle="modal">
       <i class="fa-solid fa-plus"></i> Добавить
@@ -20,7 +20,7 @@
         <th scope="col">#</th>
         <th scope="col">ID</th>
         <th scope="col">Название</th>
-        <th scope="col">Элементы управления</th>
+        <th sec:authorize="hasRole('ROLE_ADMIN')" scope="col">Элементы управления</th>
       </tr>
       </thead>
       <tbody>
@@ -28,7 +28,7 @@
         <th scope="row" th:text="${iterator.index} + 1"/>
         <td th:text="${category.id}"/>
         <td th:text="${category.name}" style="width: 60%"/>
-        <td style="width: 10%">
+        <td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
           <div class="btn-group" role="group" aria-label="Basic example">
             <a class="btn btn-info button-fixed button-sm" th:data-category="${category.id}"
                data-bs-target="#categoryEditModal" data-bs-toggle="modal"
diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html
index 7fa40a1..54c8a22 100644
--- a/src/main/resources/templates/default.html
+++ b/src/main/resources/templates/default.html
@@ -32,6 +32,8 @@
            th:classappend="${#strings.equals(activeLink, '/category')} ? 'active' : ''">Категории</a>
         <a class="nav-link" href="/categoryStudent/groupbycategory"
            th:classappend="${#strings.equals(activeLink, '/categoryStudent/groupbycategory')} ? 'active' : ''">Количество студентов в категории</a>
+        <a sec:authorize="!isAuthenticated()" class="nav-link" href="/login">Войти</a>
+        <a sec:authorize="isAuthenticated()" class="nav-link" href="/logout">Выйти</a>
       </ul>
     </div>
   </div>
diff --git a/src/main/resources/templates/drivingSchool-one.html b/src/main/resources/templates/drivingSchool-one.html
index 43c81ea..d82c770 100644
--- a/src/main/resources/templates/drivingSchool-one.html
+++ b/src/main/resources/templates/drivingSchool-one.html
@@ -9,7 +9,7 @@
 <div layout:fragment="content">
   <h1>Название: <span th:text="${drivingSchool.name}"></span></h1>
   <h2>Количество студентов: <span th:text="${#arrays.length(drivingSchool.students.toArray())}"></span></h2>
-  <div>
+  <div sec:authorize="hasRole('ROLE_ADMIN')">
     <a class="btn btn-success button-fixed"
        data-bs-target="#hireModal" data-bs-toggle="modal">
       Зачислить студента
@@ -24,7 +24,7 @@
       <th scope="col">Имя</th>
       <th scope="col">Номер телефона</th>
       <th scope="col">Категории</th>
-      <th scope="col"></th>
+      <th sec:authorize="hasRole('ROLE_ADMIN')" scope="col"></th>
     </tr>
     </thead>
     <tbody>
@@ -35,7 +35,7 @@
       <td th:text="${student.name}" />
       <td th:text="${student.phoneNumber}" />
       <td th:text="${#strings.listJoin(student.categories.![name],',')}"></td>
-      <th style="width: 20%">
+      <th sec:authorize="hasRole('ROLE_ADMIN')" style="width: 20%">
         <a class="btn btn-danger button-fixed button-sm" th:data-student="${student.id}"
            data-bs-target="#dismissModal" data-bs-toggle="modal"
            th:onclick="openModalDismiss(this.getAttribute('data-student'))">Отчислить</a>
diff --git a/src/main/resources/templates/drivingSchool.html b/src/main/resources/templates/drivingSchool.html
index 2cfe943..11cf6e0 100644
--- a/src/main/resources/templates/drivingSchool.html
+++ b/src/main/resources/templates/drivingSchool.html
@@ -7,14 +7,11 @@
 <body>
 <div layout:fragment="content">
   <div>Автошколы</div>
-  <div>
+  <div sec:authorize="hasRole('ROLE_ADMIN')">
     <a class="btn btn-success button-fixed"
        onsubmit="openModalEdit()" data-bs-target="#drivingSchoolEditModal" data-bs-toggle="modal">
       <i class="fa-solid fa-plus"></i> Добавить
     </a>
-    <!--        <a class="btn btn-info button-fixed" data-bs-toggle="modal" data-bs-target="#drivingSchoolModal">-->
-    <!--            Перейти к компании-->
-    <!--        </a>-->
   </div>
   <div class="table-responsive">
     <table class="table">
@@ -24,7 +21,7 @@
         <th scope="col">ID</th>
         <th scope="col">Название</th>
         <th scope="col">Студенты</th>
-        <th scope="col">Элементы управления</th>
+        <th sec:authorize="hasRole('ROLE_ADMIN')" scope="col">Элементы управления</th>
       </tr>
       </thead>
       <tbody>
@@ -33,7 +30,7 @@
         <td th:text="${drivingSchool.id}"/>
         <td style="width: 60%"><a th:href="@{/drivingSchool/one/{id}(id=${drivingSchool.id})}" th:text="${drivingSchool.name}"></a></td>
         <td th:text="${#arrays.length(drivingSchool.students.toArray())}" style="width: 60%"/>
-        <td style="width: 10%">
+        <td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
           <div class="btn-group" role="group" aria-label="Basic example">
             <a class="btn btn-info button-fixed button-sm" th:data-drivingSchool="${drivingSchool.id}"
                data-bs-target="#drivingSchoolEditModal" data-bs-toggle="modal"
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index a6eac40..1b01785 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -6,8 +6,7 @@
 </head>
 <body>
 <div layout:fragment="content">
-  <div>Вроде работает</div>
-  <a href="123">Ошибка :(</a>
+  <div>Добро пожаловать! Выберите страницу.</div>
 </div>
 </body>
 </html>
\ No newline at end of file
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
new file mode 100644
index 0000000..6675e44
--- /dev/null
+++ b/src/main/resources/templates/login.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{default}">
+<head>
+  <link th:href="@{/styles/login.css}" rel="stylesheet" />
+</head>
+<body>
+<div class="container-fluid" layout:fragment="content">
+  <div th:if="${param.error}" class="alert alert-danger margin-bottom">
+    Пользователь не найден или пароль указан не верно
+  </div>
+  <div th:if="${param.logout}" class="alert alert-success margin-bottom">
+    Выход успешно произведен
+  </div>
+  <div th:if="${param.created}" class="alert alert-success margin-bottom">
+    Пользователь '<span th:text="${param.created}"></span>' успешно создан
+  </div>
+  <div class="row justify-content-center align-items-center vh-100">
+    <div class="col-sm-6 col-md-4">
+      <div class="card">
+        <div class="card-body">
+          <h5 class="card-title">Авторизация</h5>
+          <form th:action="@{/login}" method="post">
+            <div class="mb-3">
+              <label htmlFor="login">Логин</label>
+              <input type="text" name="username" id="username" class="form-control"
+                     placeholder="Логин" required="true" autofocus="true"/>
+            </div>
+            <div class="mb-3">
+              <label htmlFor="login">Пароль</label>
+              <input type="password" name="password" id="password" class="form-control"
+                     placeholder="Пароль" required="true"/>
+            </div>
+            <button type="submit" class="btn btn-primary button-fixed">Войти</button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html
new file mode 100644
index 0000000..287c11f
--- /dev/null
+++ b/src/main/resources/templates/signup.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{default}">
+<body>
+<div class="container container-padding" layout:fragment="content">
+  <div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
+  <form action="#" th:action="@{/signup}" th:object="${userDto}" method="post">
+    <div class="mb-3">
+      <input type="text" class="form-control" th:field="${userDto.login}"
+             placeholder="Логин" required="true" autofocus="true" maxlength="64"/>
+    </div>
+    <div class="mb-3">
+      <input type="password" class="form-control" th:field="${userDto.password}"
+             placeholder="Пароль" required="true" minlength="6" maxlength="64"/>
+    </div>
+    <div class="mb-3">
+      <input type="password" class="form-control" th:field="${userDto.passwordConfirm}"
+             placeholder="Пароль (подтверждение)" required="true" minlength="6" maxlength="64"/>
+    </div>
+    <div class="mb-3">
+      <button type="submit" class="btn btn-success button-fixed">Создать</button>
+      <a class="btn btn-primary button-fixed" href="/login">Назад</a>
+    </div>
+  </form>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/student.html b/src/main/resources/templates/student.html
index 8aa1d77..f1a0ef2 100644
--- a/src/main/resources/templates/student.html
+++ b/src/main/resources/templates/student.html
@@ -7,7 +7,7 @@
 <body>
 <div layout:fragment="content">
   <div>Студенты</div>
-  <div>
+  <div sec:authorize="hasRole('ROLE_ADMIN')">
     <a class="btn btn-success button-fixed"
        onsubmit="openModalEdit()" data-bs-target="#studentEditModal" data-bs-toggle="modal">
       <i class="fa-solid fa-plus"></i> Добавить
@@ -23,7 +23,7 @@
         <th scope="col">Имя</th>
         <th scope="col">Номер телефона</th>
         <th scope="col">Категории</th>
-        <th scope="col">Элементы управления</th>
+        <th sec:authorize="hasRole('ROLE_ADMIN')" scope="col">Элементы управления</th>
       </tr>
       </thead>
       <tbody>
@@ -34,7 +34,7 @@
         <td th:text="${student.name}" />
         <td th:text="${student.phoneNumber}" />
         <td th:text="${#strings.listJoin(student.categories.![name],',')}"></td>
-        <td style="width: 10%">
+        <td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
           <div class="btn-group" role="group" aria-label="Basic example">
             <a class="btn btn-info button-fixed button-sm" th:data-student="${student.id}"
                data-bs-target="#studentEditModal" data-bs-toggle="modal"
diff --git a/src/main/resources/templates/users.html b/src/main/resources/templates/users.html
new file mode 100644
index 0000000..5e2c3d7
--- /dev/null
+++ b/src/main/resources/templates/users.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{default}">
+<body>
+<div class="container" layout:fragment="content">
+  <div class="table-responsive">
+    <table class="table">
+      <thead>
+      <tr>
+        <th scope="col">#</th>
+        <th scope="col">ID</th>
+        <th scope="col">Логин</th>
+        <th scope="col">Роль</th>
+      </tr>
+      </thead>
+      <tbody>
+      <tr th:each="user, iterator: ${users}">
+        <th scope="row" th:text="${iterator.index} + 1"></th>
+        <td th:text="${user.id}"></td>
+        <td th:text="${user.login}" style="width: 60%"></td>
+        <td th:text="${user.role}" style="width: 20%"></td>
+      </tr>
+      </tbody>
+    </table>
+  </div>
+  <div th:if="${totalPages > 0}" class="pagination">
+    <span style="float: left; padding: 5px 5px;">Страницы:</span>
+    <a th:each="page : ${pages}"
+       th:href="@{/users(page=${page}, size=${users.size})}"
+       th:text="${page}"
+       th:class="${page == users.number + 1} ? active">
+    </a>
+  </div>
+</div>
+</body>
+</html>
\ No newline at end of file