From 739f4c54ddbef6330016c38bff9416dad8303a63 Mon Sep 17 00:00:00 2001 From: Gabriel Copat <copatg@cardiff.ac.uk> Date: Mon, 11 Dec 2023 12:51:54 +0000 Subject: [PATCH] Added login validation with Spring Security --- build.gradle | 4 +- .../security/SecurityConfiguration.java | 49 +++++++++++++++++++ .../java/Team5/SmartTowns/users/NewUser.java | 4 ++ .../java/Team5/SmartTowns/users/User.java | 3 +- .../SmartTowns/users/UserController.java | 43 +++++++++++----- .../SmartTowns/users/UserRepository.java | 8 +-- .../SmartTowns/users/UserRepositoryJDBC.java | 41 ++++++++-------- src/main/resources/application.properties | 1 + src/main/resources/data.sql | 13 ----- src/main/resources/schema.sql | 19 ++++--- src/main/resources/static/css/login.css | 7 ++- src/main/resources/static/scripts/userPage.js | 6 +-- src/main/resources/static/sql/user-data.sql | 7 +++ .../static/sql/user-progress-data.sql | 7 +++ src/main/resources/templates/users/login.html | 8 +-- .../templates/users/userProfile.html | 3 +- 16 files changed, 155 insertions(+), 68 deletions(-) create mode 100644 src/main/java/Team5/SmartTowns/security/SecurityConfiguration.java create mode 100644 src/main/resources/static/sql/user-data.sql create mode 100644 src/main/resources/static/sql/user-progress-data.sql diff --git a/build.gradle b/build.gradle index 0025adb3..23bab4e1 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.mariadb.jdbc:mariadb-java-client:2.1.2' - testImplementation 'junit:junit:4.13.1' + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'junit:junit:4.13.1' compileOnly 'org.projectlombok:lombok' testImplementation 'org.projectlombok:lombok:1.18.28' compileOnly 'org.projectlombok:lombok' @@ -35,6 +36,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' // https://mvnrepository.com/artifact/org.webjars/openlayers implementation group: 'org.webjars', name: 'openlayers', version: '5.2.0' } diff --git a/src/main/java/Team5/SmartTowns/security/SecurityConfiguration.java b/src/main/java/Team5/SmartTowns/security/SecurityConfiguration.java new file mode 100644 index 00000000..0956fccf --- /dev/null +++ b/src/main/java/Team5/SmartTowns/security/SecurityConfiguration.java @@ -0,0 +1,49 @@ +package Team5.SmartTowns.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.JdbcUserDetailsManager; +import org.springframework.security.provisioning.UserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import javax.sql.DataSource; + + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/user/**", "/userProfile").authenticated() + .anyRequest().permitAll() + ) + .formLogin((login) -> login + .loginPage("/login").permitAll() + .defaultSuccessUrl("/userProfile") + ) + .logout((logout) -> logout.permitAll()); + + return http.build(); + } + @Bean + public PasswordEncoder passwordEncoder(){ + return NoOpPasswordEncoder.getInstance(); + } + + @Bean + public UserDetailsManager userDetailsManager(DataSource dataSource){ + JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource); + return manager; + } + + +} diff --git a/src/main/java/Team5/SmartTowns/users/NewUser.java b/src/main/java/Team5/SmartTowns/users/NewUser.java index 08799204..aa47adac 100644 --- a/src/main/java/Team5/SmartTowns/users/NewUser.java +++ b/src/main/java/Team5/SmartTowns/users/NewUser.java @@ -1,6 +1,7 @@ package Team5.SmartTowns.users; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -12,11 +13,14 @@ public class NewUser { @NotEmpty(message = "You must type in a username.") + @NotNull String name; @NotEmpty(message = "You must type in a password.") + @NotNull String password; @NotEmpty(message = "You must type in an email.") + @NotNull String email; } diff --git a/src/main/java/Team5/SmartTowns/users/User.java b/src/main/java/Team5/SmartTowns/users/User.java index 4ef65c3b..96949fb1 100644 --- a/src/main/java/Team5/SmartTowns/users/User.java +++ b/src/main/java/Team5/SmartTowns/users/User.java @@ -27,8 +27,7 @@ public class User { this.dragonProgress = dragonProgress; imgPath = findImagePath(); } - public User(int id, String email, String name) { - this.id = id; + public User(String email, String name) { this.email = email; this.name = name; imgPath = findImagePath(); diff --git a/src/main/java/Team5/SmartTowns/users/UserController.java b/src/main/java/Team5/SmartTowns/users/UserController.java index c937643d..aaa697e5 100644 --- a/src/main/java/Team5/SmartTowns/users/UserController.java +++ b/src/main/java/Team5/SmartTowns/users/UserController.java @@ -6,13 +6,14 @@ import Team5.SmartTowns.rewards.RewardsRepository; import Team5.SmartTowns.rewards.Sticker; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; -import java.util.Arrays; import java.util.List; @Controller @@ -29,6 +30,7 @@ public class UserController { public ModelAndView getLoginPage() { ModelAndView mav = new ModelAndView("users/login"); mav.addObject("user", new NewUser( "", "", "")); + System.out.println(userRepository.findUserByName("Admin").getName()); return mav; } @@ -38,16 +40,17 @@ public class UserController { // TODO VALIDATE EMAIL INPUT if (bindingResult.hasErrors()) { - System.out.println("Errors"); + ModelAndView modelAndView = new ModelAndView("users/login"); + modelAndView.addObject("errors", bindingResult); + return modelAndView; } System.out.println(user.getName()); System.out.println(user.getPassword()); + if ( userRepository.doesUserExist(user.getEmail()) ) { //TODO return modelandview for user already exists System.out.println(user.getEmail() + " already exists"); - System.out.print("LOG IN:"); - System.out.println(userRepository.userLogIn(user.email, user.password)); return mav; } else { userRepository.addUser(user.name, user.email, user.password); @@ -56,28 +59,42 @@ public class UserController { } } + @GetMapping("/userProfile") + public ModelAndView userProfile(){ + ModelAndView mav = new ModelAndView("users/userProfile"); + List<Pack> allPacks = rewardsRepository.getAllPacks(); + mav.addObject("packs", allPacks); + + User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + System.out.println(user.getUsername()); + mav.addObject("user", userRepository.findUserByName("Admin")); + mav.addAllObjects(getPackInfo("Admin", 1).getModelMap()); + + return mav; + } + + /* USER MAPPING & FUNCTIONS */ - @GetMapping("/user/{id}") - public ModelAndView getUserPage(@PathVariable int id) { + @GetMapping("/user/{username}") + public ModelAndView getUserPage(@PathVariable String username) { ModelAndView mav = new ModelAndView("users/userProfile"); List<Pack> allPacks = rewardsRepository.getAllPacks(); - mav.addObject("user", userRepository.getUserById(id)); + mav.addObject("user", userRepository.findUserByName("Admin")); mav.addObject("packs", allPacks); - userRepository.addUser("Maria", "MariaEmail", "MariaPassword"); - userRepository.doesUserExist("MariaEmail"); - mav.addAllObjects(getPackInfo(id, 1).getModelMap()); + mav.addAllObjects(getPackInfo(username, 1).getModelMap()); return mav; } - @GetMapping("/packInfo/{userID}/{packID}") - public ModelAndView getPackInfo(@PathVariable int userID, @PathVariable int packID) { + @GetMapping("/packInfo/{username}/{packID}") + public ModelAndView getPackInfo(@PathVariable String username, @PathVariable int packID) { /* Displays on page the stickers present in the pack and colour the ones the * user has acquired */ ModelAndView mav = new ModelAndView("users/userFrags :: stickersBox"); List<Sticker> allStickers = rewardsRepository.getAllStickersFromPack(packID); - List<Long> userStickers = userRepository.getUserStickersFromPack(userID, packID); + List<Long> userStickers = userRepository.getUserStickersFromPack(username, packID); + System.out.println(userStickers); diff --git a/src/main/java/Team5/SmartTowns/users/UserRepository.java b/src/main/java/Team5/SmartTowns/users/UserRepository.java index f47b120a..097598e2 100644 --- a/src/main/java/Team5/SmartTowns/users/UserRepository.java +++ b/src/main/java/Team5/SmartTowns/users/UserRepository.java @@ -5,10 +5,10 @@ import java.util.List; public interface UserRepository { List<User> getAllUsers(); - List<Long> getUserStickersFromPack(int userID, int packID); - User getUserById(int userID); - boolean unlockSticker(int userID, int packID, int stickerID); + List<Long> getUserStickersFromPack(String username, int packID); + boolean unlockSticker(String username, int packID, int stickerID); boolean addUser(String username, String email, String password); boolean doesUserExist(String email); - boolean userLogIn(String username, String password); + User findUserByEmail(String email); + User findUserByName(String name); } diff --git a/src/main/java/Team5/SmartTowns/users/UserRepositoryJDBC.java b/src/main/java/Team5/SmartTowns/users/UserRepositoryJDBC.java index 492d7b1a..69ef84a1 100644 --- a/src/main/java/Team5/SmartTowns/users/UserRepositoryJDBC.java +++ b/src/main/java/Team5/SmartTowns/users/UserRepositoryJDBC.java @@ -26,9 +26,8 @@ public class UserRepositoryJDBC implements UserRepository{ private void setUserMapper(){ userMapper = (rs, i) -> new User( - rs.getInt("id"), rs.getString("email"), - rs.getString("name") + rs.getString("username") ); } @@ -40,29 +39,24 @@ public class UserRepositoryJDBC implements UserRepository{ @Override - public User getUserById(int userID){ - String sql= "SELECT * FROM users WHERE id=?"; - List<User> result = jdbc.query(sql, userMapper, userID); - return result.isEmpty() ? null : result.get(0); + public List<Long> getUserStickersFromPack(String username, int packID) { + String sql = "SELECT stickerID FROM stickerprogress WHERE (username, packID)= (?,?)"; + return jdbc.queryForList(sql, Long.class, username, packID); } @Override - public List<Long> getUserStickersFromPack(int userID, int packID) { - String sql = "SELECT stickerID FROM stickerprogress WHERE (userID, packID)= (?,?)"; - return jdbc.queryForList(sql, Long.class, userID, packID); - } - - @Override - public boolean unlockSticker(int userID, int packID, int stickerID){ - String sql = "INSERT INTO stickerprogress (userID, packID, stickerID) VALUES (?,?,?)"; - jdbc.update(sql, userID, packID, stickerID); + public boolean unlockSticker(String username, int packID, int stickerID){ + String sql = "INSERT INTO stickerprogress (username, packID, stickerID) VALUES (?,?,?)"; + jdbc.update(sql, username, packID, stickerID); return true; } @Override public boolean addUser(String username, String email, String password){ - String query = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; + String query = "INSERT INTO users (username, email, password) VALUES (?, ?, ?);"; + String query2= "INSERT INTO authorities (username, authority) VALUES (?,?);"; jdbc.update(query, username, email, password); + jdbc.update(query2, username, "USER"); return true; } @Override @@ -70,11 +64,18 @@ public class UserRepositoryJDBC implements UserRepository{ String query = "SELECT COUNT(email) FROM users WHERE (email) = (?)"; return !(jdbc.queryForObject(query, Integer.class, email) == 0); } + @Override - public boolean userLogIn(String email, String password){ - String query = "SELECT (password) FROM users WHERE (email) = (?)"; - String dbpassword = jdbc.queryForObject(query, String.class, email); - return Objects.equals(dbpassword, password); + public User findUserByEmail(String email) { + String query = "SELECT * FROM users WHERE (email) = (?)"; + List<User> result = jdbc.query(query, userMapper, email); + return result.isEmpty() ? null : result.get(0); + } + @Override + public User findUserByName(String name) { + String query = "SELECT * FROM users WHERE (username) = (?)"; + List<User> result = jdbc.query(query, userMapper, name); + return result.isEmpty() ? null : result.get(0); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 95f46c69..52e81d21 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,3 +3,4 @@ spring.datasource.username=root spring.datasource.password=comsc spring.sql.init.mode=always +spring.sql.init.data-locations=classpath:data.sql, classpath:/static/sql/user-data.sql, classpath:/static/sql/user-progress-data.sql diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index bc731eab..b91b4f71 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,9 +1,3 @@ -DELETE FROM users; -INSERT INTO users (id, email, name, password) VALUE (1, 'admin@gmail.com', 'Admin', 'admin'); -INSERT INTO users (email, name, password) VALUE ('hannah@gmail.com', 'Hannah', 'root'); -INSERT INTO users (email, name, password) VALUE ('nigel@gmail.com', 'Nigel', 'root'); -INSERT INTO users (email, name, password) VALUE ('oscar@gmail.com', 'Oscar', 'root'); - delete from trails; insert into trails ( Name,tru) value ( 'Caerphilly Coffee Trail',false); insert into trails ( Name,tru) value ( 'Penarth Dragon Trail',true); @@ -56,10 +50,3 @@ INSERT INTO stickers (packID, stickerID, name, description, rarity) VALUE (3, 1, INSERT INTO stickers (packID, stickerID, name, description, rarity) VALUE (3, 2, 'Welsh Outline', 'Welsh Heritage', '1'); INSERT INTO stickers (packID, stickerID, name, description, rarity) VALUE (3, 3, 'Welsh Spoon', 'Welsh Heritage', '1'); -DELETE FROM stickerprogress; -INSERT INTO stickerprogress (userID, packID, stickerID) VALUE (1, 1, 1); -INSERT INTO stickerprogress (userID, packID, stickerID) VALUE (1, 1, 2); -INSERT INTO stickerprogress (userID, packID, stickerID) VALUE (1, 1, 3); -INSERT INTO stickerprogress (userID, packID, stickerID) VALUE (1, 1, 5); -INSERT INTO stickerprogress (userID, packID, stickerID) VALUE (1, 2, 1); -INSERT INTO stickerprogress (userID, packID, stickerID) VALUE (1, 2, 3); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index c7e9993b..81a0c09f 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -36,12 +36,17 @@ create table if not exists locations CREATE TABLE IF NOT EXISTS users ( - id bigint auto_increment primary key, - email varchar(128) NOT NULL , - name varchar(30) NOT NULL, + username varchar(30) primary key NOT NULL, + email varchar(128), password varchar(30) NOT NULL, - dragonProgress int, - dragonsLandmarkIDs longtext + enabled boolean default true, + roles varchar(128) +); + +CREATE TABLE IF NOT EXISTS authorities ( + id bigint primary key auto_increment NOT NULL, + username varchar(30) NOT NULL , + authority varchar(45) NOT NULL ); CREATE TABLE IF NOT EXISTS packs ( @@ -64,8 +69,8 @@ CREATE TABLE IF NOT EXISTS stickers ( CREATE TABLE IF NOT EXISTS stickerProgress ( id bigint auto_increment primary key, - userID bigint NOT NULL, - FOREIGN KEY (userID) REFERENCES users(id) + username varchar(30) NOT NULL, + FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE ON UPDATE RESTRICT, packID bigint NOT NULL, diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css index 25d221cc..023566c2 100644 --- a/src/main/resources/static/css/login.css +++ b/src/main/resources/static/css/login.css @@ -9,6 +9,8 @@ --font-size-1: 14px; --font-size-2: 200px; --font-size-3: 300px; + + --error-colour: red; } *{ @@ -363,5 +365,8 @@ and (max-device-width: 640px) { border-color: #fff; } } - +.alert { + color: var(--error-colour); + text-shadow: var(--error-colour) 0 0 10px; +} diff --git a/src/main/resources/static/scripts/userPage.js b/src/main/resources/static/scripts/userPage.js index 2c1d5069..a7b03619 100644 --- a/src/main/resources/static/scripts/userPage.js +++ b/src/main/resources/static/scripts/userPage.js @@ -1,6 +1,6 @@ -function updatePack(userid, packid) { - /* Updates the trail being shown on screen to the one requested by ID */ - $.get("/packInfo/" + userid + "/" + packid).done(function (fragment) { +function updatePack(url) { + /* Updates the trail being shown on screen */ + $.get(url).done(function (fragment) { let packRewardsWrapper = $("#packRewardsWrapper"); packRewardsWrapper.fadeTo("slow", 0, function () { diff --git a/src/main/resources/static/sql/user-data.sql b/src/main/resources/static/sql/user-data.sql new file mode 100644 index 00000000..333ec409 --- /dev/null +++ b/src/main/resources/static/sql/user-data.sql @@ -0,0 +1,7 @@ +INSERT INTO users (username, password) VALUE ('Admin', 'admin'); +INSERT INTO users (username, password) VALUE ('Hannah', 'root'); +INSERT INTO users (username, password) VALUE ('Nigel', 'root'); +INSERT INTO users (username, password) VALUE ('Oscar', 'root'); + +INSERT INTO authorities (username, authority) VALUE ('Admin', 'ADMIN'); +INSERT INTO authorities (username, authority) VALUE ('Hannah', 'USER'); diff --git a/src/main/resources/static/sql/user-progress-data.sql b/src/main/resources/static/sql/user-progress-data.sql new file mode 100644 index 00000000..e9223384 --- /dev/null +++ b/src/main/resources/static/sql/user-progress-data.sql @@ -0,0 +1,7 @@ +DELETE FROM stickerprogress; +INSERT INTO stickerprogress (username, packID, stickerID) VALUE ('Admin', 1, 1); +INSERT INTO stickerprogress (username, packID, stickerID) VALUE ('Admin', 1, 2); +INSERT INTO stickerprogress (username, packID, stickerID) VALUE ('Admin', 1, 3); +INSERT INTO stickerprogress (username, packID, stickerID) VALUE ('Admin', 1, 5); +INSERT INTO stickerprogress (username, packID, stickerID) VALUE ('Admin', 2, 1); +INSERT INTO stickerprogress (username, packID, stickerID) VALUE ('Admin', 2, 3); \ No newline at end of file diff --git a/src/main/resources/templates/users/login.html b/src/main/resources/templates/users/login.html index de23c96f..51c6222e 100644 --- a/src/main/resources/templates/users/login.html +++ b/src/main/resources/templates/users/login.html @@ -33,13 +33,15 @@ </form> </div> <div class="form-container sign-in"> - <form th:object="${user}" action="#" th:action="@{/login/register}" th:method="POST" onsubmit="return loginFormValidation()"> + <form name="f" th:action="@{/login}" th:method="POST"> <h1>Sign In</h1> + <div th:if="${param.error}" class="alert alert-error">Invalid Username or Password</div> + <div th:if="${param.logout}" class="alert alert-success">Successfully Logged out</div> <label> - <input class="input" th:field="*{email}" id="login-email" type="email" placeholder="Email"> + <input class="input" id="username" type="text" name="username" placeholder="Email"> </label> <label> - <input class="input" th:field="*{password}" id="login-password" type="password" placeholder="Password"> + <input class="input" id="password" type="password" name="password" placeholder="Password"> </label> <a href="#" class="text">Forget Your Password?</a> <button type="submit">Sign In</button> diff --git a/src/main/resources/templates/users/userProfile.html b/src/main/resources/templates/users/userProfile.html index 94f46709..edbe1391 100644 --- a/src/main/resources/templates/users/userProfile.html +++ b/src/main/resources/templates/users/userProfile.html @@ -39,7 +39,8 @@ <div th:each="pack : ${packs}" class="packContainer"> <img class="packImg" th:src="@{'../' + ${pack.getDisplayImg()}}" th:id="'packImg' + ${pack.getId()}" th:alt="${pack.getName()}" - th:onclick="'updatePack(' + ${user.getId()} +',' + ${pack.getId()} +');'"> + th:data-url="@{/packInfo/{username}/{packID}(username=${user.getName()}, packID=${pack.getId()})}" + onclick="updatePack(this.getAttribute('data-url'))"> <h4 class="packName" th:text="${pack.getName()}"></h4> </div> </div> -- GitLab