diff --git a/server/build.gradle b/server/build.gradle index c4ffdba..30e4c3f 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -17,6 +17,17 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // Spring security + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-test' + + // JSON web token + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2', + // Uncomment the next line if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms: + //'org.bouncycastle:bcprov-jdk15on:1.60', + 'io.jsonwebtoken:jjwt-jackson:0.11.2' // or 'io.jsonwebtoken:jjwt-gson:0.11.2' for gson } test { diff --git a/server/src/main/java/com/vpr/server/MainController.java b/server/src/main/java/com/vpr/server/controller/EventController.java similarity index 52% rename from server/src/main/java/com/vpr/server/MainController.java rename to server/src/main/java/com/vpr/server/controller/EventController.java index a56d476..158f84c 100644 --- a/server/src/main/java/com/vpr/server/MainController.java +++ b/server/src/main/java/com/vpr/server/controller/EventController.java @@ -1,72 +1,40 @@ -package com.vpr.server; +package com.vpr.server.controller; +import com.vpr.server.data.Event; +import com.vpr.server.data.User; +import com.vpr.server.data.UserEvent; +import com.vpr.server.repository.EventRepository; +import com.vpr.server.repository.UserEventRepository; +import com.vpr.server.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.server.ResponseStatusException; -import java.sql.Date; import java.sql.Time; import java.text.SimpleDateFormat; -import java.util.Optional; -@Controller // This means that this class is a Controller -@RequestMapping(path = "/vpr") // This means URL's start with /demo (after Application path) -public class MainController { - - // This means to get the bean called userRepository - // Which is auto-generated by Spring, we will use it to handle the data +@Controller +@RequestMapping(path = "/event") +public class EventController { @Autowired - private com.vpr.server.UserRepository userRepository; - + private UserRepository userRepository; @Autowired private EventRepository eventRepository; - @Autowired private UserEventRepository userEventRepository; - // POST-request at /add with request parameter - // @ResponseBody means the returned String is the response, not a view name - @PostMapping(path = "/add-user") + /****************** + * POST-ENDPOINTS * + ******************/ + + @PostMapping(path = "/add") public @ResponseBody - String addNewUser( - @RequestParam String name, - @RequestParam String forename, - @RequestParam String password, - @RequestParam String isAdmin - ) { - - com.vpr.server.User user = new com.vpr.server.User(); - - // TODO set correct token and password - user.setName(name); - user.setForename(forename); - user.setPassword(password); - user.setToken("test"); - user.setAdmin(isAdmin.equals("1")); - - userRepository.save(user); - return "Saved"; - } - - @PostMapping(path = "/login") - public @ResponseBody - String login( - @RequestParam String login, - @RequestParam String password - ) { - User user = userRepository.findByLoginAndPassword(login, password); - if(user != null){ - return "" + user.getId(); - } - return "-1"; - } - - @PostMapping(path = "/add-event") - public @ResponseBody - ResponseEntity addEvent( + String addEvent( @RequestParam Integer userId, @RequestParam String date, @RequestParam String name, @@ -78,14 +46,14 @@ public class MainController { ) { String errorString = ""; - com.vpr.server.Event event = new com.vpr.server.Event(); + Event event = new Event(); System.out.println(name.length() + ". name " + name); if (name.length() > 3) { event.setName(name); } else { System.out.println("NAME IST ZU KURZ"); - return new ResponseEntity(HttpStatus.NOT_ACCEPTABLE); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Format nicht korrekt"); } try { @@ -108,7 +76,7 @@ public class MainController { event.setFullDay(isFullDay); event.setPrivate(isPrivate); - com.vpr.server.UserEvent userEvent = new com.vpr.server.UserEvent(); + UserEvent userEvent = new UserEvent(); try { System.out.println("date " + date); @@ -116,6 +84,7 @@ public class MainController { userEvent.setDate(new java.sql.Date(simpleDateFormat.parse(date).getTime())); } catch (Exception e) { System.out.println("DATE FORMAT NOT CORRECT"); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Format nicht korrekt"); } userEvent.setEvent(event); @@ -128,11 +97,10 @@ public class MainController { eventRepository.save(event); userEventRepository.save(userEvent); - - return new ResponseEntity(HttpStatus.OK); + return ""; } - @PostMapping(path = "/del-event") + @PostMapping(path = "/del") public @ResponseBody String addEvent(@RequestParam Integer eventId) { eventRepository.deleteUserEventsById(Long.valueOf(eventId)); @@ -140,28 +108,10 @@ public class MainController { return "Deleted"; } - // GET-request at /all-users - // returns JSON-data - @GetMapping(path = "/all-users") - public @ResponseBody - Object[] getAllUsers() { - return userRepository.findAllUsernames(); - } - - // POST-request at /all-events - // returns JSON-data - @PostMapping(path = "/all-events") + @PostMapping(path = "/all") public @ResponseBody Object[] getAllEvents(@RequestParam long userId) { return eventRepository.findAllVisibleByUserId(userId); } - - @GetMapping(path = "/all-events-test") - public @ResponseBody - Iterable getAllEventsTest() { - return eventRepository.findAll(); - } - - -} \ No newline at end of file +} diff --git a/server/src/main/java/com/vpr/server/controller/MainController.java b/server/src/main/java/com/vpr/server/controller/MainController.java new file mode 100644 index 0000000..368ce48 --- /dev/null +++ b/server/src/main/java/com/vpr/server/controller/MainController.java @@ -0,0 +1,36 @@ +package com.vpr.server.controller; + +import com.vpr.server.data.Event; +import com.vpr.server.data.User; +import com.vpr.server.data.UserEvent; +import com.vpr.server.repository.EventRepository; +import com.vpr.server.repository.UserEventRepository; +import com.vpr.server.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.sql.Time; +import java.text.SimpleDateFormat; + +@Controller // This means that this class is a Controller +@RequestMapping(path = "/vpr") // This means URL's start with /demo (after Application path) +public class MainController { + + // This means to get the bean called userRepository + // Which is auto-generated by Spring, we will use it to handle the data + @Autowired + private UserRepository userRepository; + @Autowired + private EventRepository eventRepository; + @Autowired + private UserEventRepository userEventRepository; + + @GetMapping(path = "/status-test") + public String statusTest(){ + throw new ResponseStatusException(HttpStatus.I_AM_A_TEAPOT, "TestTestTest"); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/vpr/server/controller/UserController.java b/server/src/main/java/com/vpr/server/controller/UserController.java new file mode 100644 index 0000000..be61bdc --- /dev/null +++ b/server/src/main/java/com/vpr/server/controller/UserController.java @@ -0,0 +1,108 @@ +package com.vpr.server.controller; + +import com.vpr.server.data.User; +import com.vpr.server.repository.UserRepository; +import com.vpr.server.security.Hasher; +import com.vpr.server.security.Token; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +@Controller +@RequestMapping(path = "/user") +public class UserController { + @Autowired + private UserRepository userRepository; + + /****************** + * POST-ENDPOINTS * + ******************/ + + @PostMapping(path = "/add") + public @ResponseBody + String addNewUser( + @RequestParam String name, + @RequestParam String forename, + @RequestParam String login, + @RequestParam String password, + @RequestParam String isAdmin + ) { + byte[] salt = Hasher.GenerateSalt(); + byte[] hash; + try { + hash = Hasher.HashPassword(password, salt); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Fehler beim hashen"); + } + + User user = new User(); + + // TODO set correct token and password + user.setName(name); + user.setForename(forename); + user.setLogin(login); + user.setPassword(hash); + user.setSalt(salt); + user.setToken("test"); + user.setAdmin(isAdmin.equals("1")); + + userRepository.save(user); + return "" + user.getId(); + } + + @PostMapping(path = "/login") + public @ResponseBody + String login( + @RequestParam String login, + @RequestParam String password + ) { + User user = userRepository.findByLogin(login); + if (user == null) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Falscher login"); + } + + byte[] salt = user.getSalt(); + byte[] hash; + try { + hash = Hasher.HashPassword(password, salt); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Fehler beim hashen"); + } + + if (Arrays.equals(user.getPassword(), hash)) { + System.out.println(user.getLogin() + " is now logged in."); + System.out.println(Token.Generate(user.getLogin())); + System.out.println(Token.Verify(Token.Generate(user.getLogin()), user.getLogin())); + return "" + user.getId(); + } + System.out.println(user.getLogin() + " failed to logged in."); + System.out.println("entered : " + javax.xml.bind.DatatypeConverter.printHexBinary(hash)); + System.out.println("required: " + javax.xml.bind.DatatypeConverter.printHexBinary(user.getPassword())); + + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Falscher login"); + } + + @PostMapping(path = "/del") + public @ResponseBody String deleteUser(@RequestParam Integer userId) { + userRepository.deleteById(Long.valueOf(userId)); + return "Deleted"; + } + + /***************** + * GET-ENDPOINTS * + *****************/ + + @GetMapping(path = "/all") + public @ResponseBody + Object[] getAllUsers() { + return userRepository.findAllUsernames(); + } +} diff --git a/server/src/main/java/com/vpr/server/DateEvent.java b/server/src/main/java/com/vpr/server/data/DateEvent.java similarity index 98% rename from server/src/main/java/com/vpr/server/DateEvent.java rename to server/src/main/java/com/vpr/server/data/DateEvent.java index 6f27461..36fd68c 100644 --- a/server/src/main/java/com/vpr/server/DateEvent.java +++ b/server/src/main/java/com/vpr/server/data/DateEvent.java @@ -1,4 +1,4 @@ -package com.vpr.server; +package com.vpr.server.data; import java.sql.Date; import java.sql.Time; diff --git a/server/src/main/java/com/vpr/server/Event.java b/server/src/main/java/com/vpr/server/data/Event.java similarity index 98% rename from server/src/main/java/com/vpr/server/Event.java rename to server/src/main/java/com/vpr/server/data/Event.java index 6a1fcd6..773dbfb 100644 --- a/server/src/main/java/com/vpr/server/Event.java +++ b/server/src/main/java/com/vpr/server/data/Event.java @@ -1,4 +1,4 @@ -package com.vpr.server; +package com.vpr.server.data; import javax.persistence.*; import java.sql.Time; diff --git a/server/src/main/java/com/vpr/server/User.java b/server/src/main/java/com/vpr/server/data/User.java similarity index 84% rename from server/src/main/java/com/vpr/server/User.java rename to server/src/main/java/com/vpr/server/data/User.java index 915eb23..ec69209 100644 --- a/server/src/main/java/com/vpr/server/User.java +++ b/server/src/main/java/com/vpr/server/data/User.java @@ -1,4 +1,4 @@ -package com.vpr.server; +package com.vpr.server.data; import javax.persistence.*; import java.util.List; @@ -21,7 +21,10 @@ public class User { private String login; @Column(name="password", nullable=false) - private String password; + private byte[] password; + + @Column(name="salt", nullable=false) + private byte[] salt; @Column(name="token") private String token; @@ -68,14 +71,22 @@ public class User { this.login = login; } - public String getPassword() { + public byte[] getPassword() { return password; } - public void setPassword(String password) { + public void setPassword(byte[] password) { this.password = password; } + public byte[] getSalt() { + return salt; + } + + public void setSalt(byte[] salt) { + this.salt = salt; + } + public String getToken() { return token; } diff --git a/server/src/main/java/com/vpr/server/UserEvent.java b/server/src/main/java/com/vpr/server/data/UserEvent.java similarity index 96% rename from server/src/main/java/com/vpr/server/UserEvent.java rename to server/src/main/java/com/vpr/server/data/UserEvent.java index 3cc2582..cbf2859 100644 --- a/server/src/main/java/com/vpr/server/UserEvent.java +++ b/server/src/main/java/com/vpr/server/data/UserEvent.java @@ -1,8 +1,7 @@ -package com.vpr.server; +package com.vpr.server.data; import javax.persistence.*; import java.sql.Date; -import java.util.List; // @Entity creates a table out of this class with Hibernate // @Table defines the table-name diff --git a/server/src/main/java/com/vpr/server/UserEventId.java b/server/src/main/java/com/vpr/server/data/UserEventId.java similarity index 95% rename from server/src/main/java/com/vpr/server/UserEventId.java rename to server/src/main/java/com/vpr/server/data/UserEventId.java index d8e1009..4551603 100644 --- a/server/src/main/java/com/vpr/server/UserEventId.java +++ b/server/src/main/java/com/vpr/server/data/UserEventId.java @@ -1,4 +1,4 @@ -package com.vpr.server; +package com.vpr.server.data; import java.io.Serializable; import java.sql.Date; diff --git a/server/src/main/java/com/vpr/server/EventRepository.java b/server/src/main/java/com/vpr/server/repository/EventRepository.java similarity index 95% rename from server/src/main/java/com/vpr/server/EventRepository.java rename to server/src/main/java/com/vpr/server/repository/EventRepository.java index 9ba909e..af93e94 100644 --- a/server/src/main/java/com/vpr/server/EventRepository.java +++ b/server/src/main/java/com/vpr/server/repository/EventRepository.java @@ -1,11 +1,11 @@ -package com.vpr.server; +package com.vpr.server.repository; +import com.vpr.server.data.Event; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import javax.transaction.Transactional; -import java.util.List; // This will be AUTO IMPLEMENTED by Spring into a Bean called eventRepository // CRUD refers Create, Read, Update, Delete diff --git a/server/src/main/java/com/vpr/server/UserEventRepository.java b/server/src/main/java/com/vpr/server/repository/UserEventRepository.java similarity index 68% rename from server/src/main/java/com/vpr/server/UserEventRepository.java rename to server/src/main/java/com/vpr/server/repository/UserEventRepository.java index d5721aa..f34219c 100644 --- a/server/src/main/java/com/vpr/server/UserEventRepository.java +++ b/server/src/main/java/com/vpr/server/repository/UserEventRepository.java @@ -1,11 +1,8 @@ -package com.vpr.server; +package com.vpr.server.repository; -import org.springframework.data.jpa.repository.Query; +import com.vpr.server.data.UserEvent; import org.springframework.data.repository.CrudRepository; -import java.sql.Date; -import java.util.List; - // This will be AUTO IMPLEMENTED by Spring into a Bean called eventListRepository // CRUD refers Create, Read, Update, Delete diff --git a/server/src/main/java/com/vpr/server/UserRepository.java b/server/src/main/java/com/vpr/server/repository/UserRepository.java similarity index 67% rename from server/src/main/java/com/vpr/server/UserRepository.java rename to server/src/main/java/com/vpr/server/repository/UserRepository.java index eb2ba4b..cf8dd1f 100644 --- a/server/src/main/java/com/vpr/server/UserRepository.java +++ b/server/src/main/java/com/vpr/server/repository/UserRepository.java @@ -1,13 +1,11 @@ -package com.vpr.server; +package com.vpr.server.repository; +import com.vpr.server.data.User; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; -import java.util.List; - // This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository // CRUD refers Create, Read, Update, Delete - public interface UserRepository extends CrudRepository { @Query(value = "SELECT u.id, u.name, u.forename " + @@ -15,7 +13,11 @@ public interface UserRepository extends CrudRepository { nativeQuery = true) Object[] findAllUsernames(); - com.vpr.server.User findById(long id); + User findById(long id); - com.vpr.server.User findByLoginAndPassword(String login, String password); + User findByLogin(String login); + + User findByLoginAndPassword(String login, byte[] password); + + void deleteById(long id); } \ No newline at end of file diff --git a/server/src/main/java/com/vpr/server/security/Hasher.java b/server/src/main/java/com/vpr/server/security/Hasher.java new file mode 100644 index 0000000..287c953 --- /dev/null +++ b/server/src/main/java/com/vpr/server/security/Hasher.java @@ -0,0 +1,28 @@ +package com.vpr.server.security; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +public class Hasher { + + public static byte[] HashPassword(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { + // Credit: https://www.baeldung.com/java-password-hashing + // Generate hash with PBKDF2 + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return factory.generateSecret(spec).getEncoded(); + } + + public static byte[] GenerateSalt(){ + // Credit: https://www.baeldung.com/java-password-hashing + // Create a salt + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[16]; + random.nextBytes(salt); + return salt; + } +} diff --git a/server/src/main/java/com/vpr/server/security/Token.java b/server/src/main/java/com/vpr/server/security/Token.java new file mode 100644 index 0000000..4f9a4c0 --- /dev/null +++ b/server/src/main/java/com/vpr/server/security/Token.java @@ -0,0 +1,26 @@ +package com.vpr.server.security; + +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.security.Key; + +public class Token { + + private static Key KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); + + public static String Generate(String subject){ + return Jwts.builder().setSubject(subject).signWith(KEY).compact(); + } + + public static boolean Verify(String jws, String subject){ + try { + assert Jwts.parserBuilder().setSigningKey(KEY).build().parseClaimsJws(jws) + .getBody().getSubject().equals(subject); + return true; + } catch (JwtException e) { + return false; + } + } +} diff --git a/server/src/main/java/com/vpr/server/security/WebSecurityConfig.java b/server/src/main/java/com/vpr/server/security/WebSecurityConfig.java new file mode 100644 index 0000000..b40aa85 --- /dev/null +++ b/server/src/main/java/com/vpr/server/security/WebSecurityConfig.java @@ -0,0 +1,31 @@ +package com.vpr.server.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/", "/home").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .logout() + .permitAll(); + } + +}