diff --git a/.gitignore b/.gitignore
index f599cbe..c25374e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,4 @@ hs_err_pid*
build/
.gradle/
classes/
+out/
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a518043..fd67b53 100644
--- a/build.gradle
+++ b/build.gradle
@@ -46,8 +46,16 @@ configurations {
dependencies {
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-web')
+ compile('net.sourceforge.nekohtml:nekohtml:1.9.21')
compile('org.springframework.boot:spring-boot-starter-data-mongodb')
compile("de.flapdoodle.embed:de.flapdoodle.embed.mongo")
+ compile group: 'org.springframework.boot', name: 'spring-boot-starter-security'
+ compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.7.0'
+ compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.1'
+ compile group: 'org.springframework', name: 'spring-aspects'
+
+ compileOnly 'org.projectlombok:lombok:1.16.18'
+
compile group: 'com.rabbitmq', name: 'amqp-client', version: '5.0.0'
runtime('com.h2database:h2')
diff --git a/src/main/java/ro/ubb/istudent/aspects/Loggable.java b/src/main/java/ro/ubb/istudent/aspects/Loggable.java
new file mode 100755
index 0000000..e7fcea9
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/aspects/Loggable.java
@@ -0,0 +1,11 @@
+package ro.ubb.istudent.aspects;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = ElementType.METHOD)
+public @interface Loggable {
+}
diff --git a/src/main/java/ro/ubb/istudent/aspects/LogginAspect.java b/src/main/java/ro/ubb/istudent/aspects/LogginAspect.java
new file mode 100755
index 0000000..4b142e9
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/aspects/LogginAspect.java
@@ -0,0 +1,30 @@
+package ro.ubb.istudent.aspects;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+
+@Aspect
+@Component
+public class LogginAspect {
+
+ private Logger logger = LoggerFactory.getLogger(LogginAspect.class);
+
+ @Before("execution(public * *(..)) && @annotation(ro.ubb.istudent.aspects.Loggable)")
+ public void logBefore(JoinPoint joinPoint) throws Throwable {
+ String userName = SecurityContextHolder.getContext().getAuthentication().getName();
+
+ String logInfo = "user: " + userName + " called method: " + joinPoint.getSignature().getName() +
+ "with params: " + Arrays.toString(joinPoint.getArgs());
+
+ logger.info(logInfo);
+
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/config/CorsFilter.java b/src/main/java/ro/ubb/istudent/config/CorsFilter.java
new file mode 100755
index 0000000..551e020
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/config/CorsFilter.java
@@ -0,0 +1,57 @@
+package ro.ubb.istudent.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class CorsFilter implements Filter {
+
+ private final SecurityTokenProperties tokenProperties;
+
+ @Autowired
+ public CorsFilter(SecurityTokenProperties tokenProperties) {
+ this.tokenProperties = tokenProperties;
+ }
+
+ public void init(FilterConfig filterConfig) {
+ }
+
+ /**
+ * The doFilter method of the Filter is called by the container
+ * each time a request/response pair is passed through the chain due to a
+ * client request for a resource at the end of the chain. The FilterChain
+ * passed in to this method allows the Filter to pass on the request and
+ * response to the next entity in the chain.
+ *
+ * Directly set headers on the response after invocation of the next
+ * entity in the filter chain.
+ *
+ * @param req The request to process
+ * @param res The response associated with the request
+ * @param chain Provides access to the next filter in the chain for this
+ * filter to pass the request and response to for further
+ * processing
+ * @throws IOException if an I/O error occurs during this filter's
+ * processing of the request
+ * @throws ServletException if the processing fails for any other reason
+ */
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletResponse response = (HttpServletResponse) res;
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
+ response.setHeader("Access-Control-Max-Age", "3600");
+ response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept , Cache-Control , " +
+ tokenProperties.getHeader());
+ chain.doFilter(req, res);
+ }
+
+ public void destroy() {
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/config/SecurityTokenProperties.java b/src/main/java/ro/ubb/istudent/config/SecurityTokenProperties.java
new file mode 100755
index 0000000..f548f83
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/config/SecurityTokenProperties.java
@@ -0,0 +1,20 @@
+package ro.ubb.istudent.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+@Data
+public class SecurityTokenProperties {
+
+ @Value("${security.token.secretKey}")
+ private String secretKey;
+
+ @Value("${security.token.header}")
+ private String header;
+
+ @Value("${security.token.expiration}")
+ private Long expiration;
+
+}
diff --git a/src/main/java/ro/ubb/istudent/config/SwaggerConfig.java b/src/main/java/ro/ubb/istudent/config/SwaggerConfig.java
new file mode 100755
index 0000000..a4d29fa
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/config/SwaggerConfig.java
@@ -0,0 +1,33 @@
+package ro.ubb.istudent.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("ro.ubb.istudent.controller"))
+ .build();
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("Rest info")
+ .description("Information about rest endpoints")
+ .version("1.0")
+ .build();
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/config/WebSecurityConfiguration.java b/src/main/java/ro/ubb/istudent/config/WebSecurityConfiguration.java
new file mode 100755
index 0000000..6f5bdf5
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/config/WebSecurityConfiguration.java
@@ -0,0 +1,122 @@
+package ro.ubb.istudent.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.mongodb.config.EnableMongoAuditing;
+import org.springframework.http.HttpMethod;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.access.channel.ChannelProcessingFilter;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import ro.ubb.istudent.security.AuthenticationTokenFilter;
+import ro.ubb.istudent.security.EntryPointUnauthorizedHandler;
+
+@EnableWebSecurity
+@EnableScheduling
+@Configuration
+@Order(1)
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@EnableMongoAuditing
+public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private final CorsFilter corsFilter;
+ private final UserDetailsService userDetailsService;
+ private final EntryPointUnauthorizedHandler unauthorizedHandler;
+
+ @Autowired
+ public WebSecurityConfiguration(CorsFilter corsFilter, UserDetailsService userDetailsService, EntryPointUnauthorizedHandler unauthorizedHandler) {
+ this.corsFilter = corsFilter;
+ this.userDetailsService = userDetailsService;
+ this.unauthorizedHandler = unauthorizedHandler;
+ }
+
+ public WebSecurityConfiguration(boolean disableDefaults, CorsFilter corsFilter, UserDetailsService userDetailsService, EntryPointUnauthorizedHandler unauthorizedHandler) {
+ super(disableDefaults);
+ this.corsFilter = corsFilter;
+ this.userDetailsService = userDetailsService;
+ this.unauthorizedHandler = unauthorizedHandler;
+ }
+
+
+ @Autowired
+ public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
+ authenticationManagerBuilder
+ .userDetailsService(this.userDetailsService)
+ .passwordEncoder(passwordEncoder());
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Override
+ protected void configure(HttpSecurity httpSecurity) throws Exception {
+ httpSecurity
+ .addFilterBefore(corsFilter, ChannelProcessingFilter.class)
+ .csrf()
+ .disable()
+ .exceptionHandling()
+ .authenticationEntryPoint(this.unauthorizedHandler)
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .authorizeRequests()
+ // note: you can add as well js, css, png etc files if needed
+ // just follow the same pattern changing the file extension
+ // for static file serving check also the application.properties file
+ // you should indicate the path to resources -> static-location
+ .regexMatchers("/").permitAll()
+ .regexMatchers("/.*.html").permitAll()
+ .regexMatchers("/.*.js").permitAll()
+ .regexMatchers("/.*.css").permitAll()
+ .regexMatchers("/.*.png").permitAll()
+ .regexMatchers("/.*.jpg").permitAll()
+ .regexMatchers("/.*.jpeg").permitAll()
+ .antMatchers("/login").permitAll()
+ .antMatchers("/user/save").permitAll()
+ .antMatchers("/v2/api-docs").permitAll()
+ .antMatchers(HttpMethod.OPTIONS).permitAll()
+ .anyRequest().authenticated();
+ httpSecurity
+ .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
+ }
+
+ @Bean
+ public AuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
+ AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
+ authenticationTokenFilter.setAuthenticationManager(authenticationManager());
+ return authenticationTokenFilter;
+ }
+
+ @Configuration
+ public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
+
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry.addViewController("/").setViewName("forward: public/index.html");
+ super.addViewControllers(registry);
+ }
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/controller/AuthenticationController.java b/src/main/java/ro/ubb/istudent/controller/AuthenticationController.java
new file mode 100755
index 0000000..caa8e71
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/controller/AuthenticationController.java
@@ -0,0 +1,95 @@
+package ro.ubb.istudent.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import ro.ubb.istudent.aspects.Loggable;
+import ro.ubb.istudent.domain.ValidToken;
+import ro.ubb.istudent.dto.AuthenticationRequest;
+import ro.ubb.istudent.dto.AuthenticationResponse;
+import ro.ubb.istudent.repository.ValidTokenRepository;
+import ro.ubb.istudent.security.TokenUtils;
+import ro.ubb.istudent.util.HttpHeaders;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+
+@RestController
+public class AuthenticationController {
+
+ private final AuthenticationManager authenticationManager;
+ private final TokenUtils tokenUtils;
+ private final UserDetailsService userDetailsService;
+ private final ValidTokenRepository validTokenRepository;
+
+ @Autowired
+ public AuthenticationController(TokenUtils tokenUtils, AuthenticationManager authenticationManager, UserDetailsService userDetailsService, ValidTokenRepository validTokenRepository) {
+ this.tokenUtils = tokenUtils;
+ this.authenticationManager = authenticationManager;
+ this.userDetailsService = userDetailsService;
+ this.validTokenRepository = validTokenRepository;
+ }
+
+ @Loggable
+ @RequestMapping(path = "/login", method = RequestMethod.POST)
+ public ResponseEntity> authenticationRequest(@RequestBody AuthenticationRequest authenticationRequest)
+ throws AuthenticationException {
+
+ // Perform the authentication
+ String userNameOrEmail = authenticationRequest.getEmail() != null ? authenticationRequest.getEmail() : authenticationRequest.getUserName();
+
+ Authentication authentication = authenticationManager.authenticate(
+ new UsernamePasswordAuthenticationToken(
+ userNameOrEmail,
+ authenticationRequest.getPassword()
+ ));
+
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ // Reload password post-authentication so we can generate token
+ UserDetails userDetails = userDetailsService.loadUserByUsername(userNameOrEmail);
+ String token = tokenUtils.generateToken(userDetails);
+ Date expirationDate = tokenUtils.getExpirationDateFromToken(token);
+
+ validTokenRepository.save(ValidToken.builder()
+ .token(token)
+ .expirationDate(expirationDate)
+ .build());
+
+ return ResponseEntity.ok(new AuthenticationResponse(token));
+ }
+
+ @RequestMapping(path = "/logoutMe", method = RequestMethod.GET)
+ @Transactional
+ public ResponseEntity> logout(HttpServletRequest httpRequest, HttpServletResponse response) {
+ String authToken = httpRequest.getHeader(HttpHeaders.AUTH_TOKEN);
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ UserDetails userDetails =
+ (UserDetails) authentication.getPrincipal();
+
+ if (tokenUtils.validateToken(authToken, userDetails)) {
+ new SecurityContextLogoutHandler().logout(httpRequest, response, authentication);
+ validTokenRepository.deleteByToken(authToken);
+ return ResponseEntity.ok("done");
+ } else {
+ return ResponseEntity.ok("bad");
+ }
+
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/controller/UserController.java b/src/main/java/ro/ubb/istudent/controller/UserController.java
new file mode 100755
index 0000000..930dda7
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/controller/UserController.java
@@ -0,0 +1,52 @@
+package ro.ubb.istudent.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import ro.ubb.istudent.dto.UserDTO;
+import ro.ubb.istudent.service.UserService;
+
+@RestController
+@RequestMapping("/user")
+public class UserController {
+
+ private final UserService userService;
+
+ @Autowired
+ public UserController(UserService userService) {
+ this.userService = userService;
+ }
+
+ @RequestMapping(value = "/save", method = RequestMethod.POST)
+ public ResponseEntity> saveUser(@RequestBody UserDTO userDTO) {
+ boolean saved = false;
+ try {
+ saved = userService.saveUser(userDTO);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return ResponseEntity.ok(saved);
+ }
+
+ @RequestMapping(value = "/update", method = RequestMethod.POST)
+ public ResponseEntity> updateUser(@RequestBody UserDTO userDTO) {
+ boolean saved = false;
+ try {
+ saved = userService.saveUser(userDTO);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return ResponseEntity.ok(saved);
+ }
+
+ @RequestMapping(value = "/findByEmail/{email:.+}", method = RequestMethod.GET)
+ public ResponseEntity> getUserByEmail(@PathVariable String email){
+ return ResponseEntity.ok(userService.findByEmail(email));
+ }
+
+ @RequestMapping(value = "/findByUserName/{username}", method = RequestMethod.GET)
+ public ResponseEntity> getUserByUserName(@PathVariable String username) {
+ return ResponseEntity.ok(userService.findByUserName(username));
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/domain/Gender.java b/src/main/java/ro/ubb/istudent/domain/Gender.java
new file mode 100644
index 0000000..2f5fa6b
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/domain/Gender.java
@@ -0,0 +1,6 @@
+package ro.ubb.istudent.domain;
+
+public enum Gender {
+ M,
+ F
+}
diff --git a/src/main/java/ro/ubb/istudent/domain/User.java b/src/main/java/ro/ubb/istudent/domain/User.java
new file mode 100755
index 0000000..2b09c31
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/domain/User.java
@@ -0,0 +1,45 @@
+package ro.ubb.istudent.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Document(collection = "users")
+public class User {
+
+ @Id
+ private ObjectId id;
+
+ private String email;
+
+ private String userName;
+
+ private String password;
+
+ private String address;
+
+ private String phoneNumber;
+
+ private Integer age;
+
+ private Gender gender;
+
+ private List roles;
+
+ public GrantedAuthority getAuthority() {
+ return new SimpleGrantedAuthority(roles.get(0).name());
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/domain/UserRole.java b/src/main/java/ro/ubb/istudent/domain/UserRole.java
new file mode 100755
index 0000000..4aa0f11
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/domain/UserRole.java
@@ -0,0 +1,7 @@
+package ro.ubb.istudent.domain;
+
+public enum UserRole {
+ ADMIN,
+ TEACHER,
+ STUDENT
+}
diff --git a/src/main/java/ro/ubb/istudent/domain/ValidToken.java b/src/main/java/ro/ubb/istudent/domain/ValidToken.java
new file mode 100755
index 0000000..f4173a0
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/domain/ValidToken.java
@@ -0,0 +1,26 @@
+package ro.ubb.istudent.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Date;
+
+@Builder
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Document(collection = "valid_tokens")
+public class ValidToken {
+
+ @Id
+ private ObjectId id;
+
+ private String token;
+
+ private Date expirationDate;
+}
diff --git a/src/main/java/ro/ubb/istudent/dto/AuthenticationRequest.java b/src/main/java/ro/ubb/istudent/dto/AuthenticationRequest.java
new file mode 100755
index 0000000..0b1f184
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/dto/AuthenticationRequest.java
@@ -0,0 +1,18 @@
+package ro.ubb.istudent.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthenticationRequest {
+
+ private String email;
+ private String userName;
+ private String password;
+
+}
diff --git a/src/main/java/ro/ubb/istudent/dto/AuthenticationResponse.java b/src/main/java/ro/ubb/istudent/dto/AuthenticationResponse.java
new file mode 100755
index 0000000..aa82baf
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/dto/AuthenticationResponse.java
@@ -0,0 +1,14 @@
+package ro.ubb.istudent.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthenticationResponse {
+
+ private String token;
+
+}
diff --git a/src/main/java/ro/ubb/istudent/dto/SecurityUser.java b/src/main/java/ro/ubb/istudent/dto/SecurityUser.java
new file mode 100755
index 0000000..965285b
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/dto/SecurityUser.java
@@ -0,0 +1,62 @@
+package ro.ubb.istudent.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import ro.ubb.istudent.domain.UserRole;
+
+import java.util.Collection;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class SecurityUser implements UserDetails {
+
+ private String id;
+ private String email;
+ private String username;
+ private String password;
+ private List roles;
+ private Collection extends GrantedAuthority> authorities;
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getUsername() {
+ return email;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return authorities;
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/dto/UserDTO.java b/src/main/java/ro/ubb/istudent/dto/UserDTO.java
new file mode 100755
index 0000000..183aa4e
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/dto/UserDTO.java
@@ -0,0 +1,29 @@
+package ro.ubb.istudent.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ro.ubb.istudent.domain.Gender;
+import ro.ubb.istudent.domain.UserRole;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserDTO implements Serializable {
+
+ private String id;
+ private String userName;
+ private String email;
+ private String password;
+ private String address;
+ private String phoneNumber;
+ private Integer age;
+ private Gender gender;
+ private List roles;
+
+}
diff --git a/src/main/java/ro/ubb/istudent/mappers/DTOToEntityMapper.java b/src/main/java/ro/ubb/istudent/mappers/DTOToEntityMapper.java
new file mode 100755
index 0000000..6a81a56
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/mappers/DTOToEntityMapper.java
@@ -0,0 +1,29 @@
+package ro.ubb.istudent.mappers;
+
+import org.bson.types.ObjectId;
+import org.springframework.stereotype.Component;
+import ro.ubb.istudent.domain.User;
+import ro.ubb.istudent.dto.UserDTO;
+
+@Component
+public class DTOToEntityMapper {
+
+ // perform mapping logic
+ public User toUser(UserDTO userDTO) {
+ User user = User.builder()
+ .email(userDTO.getEmail())
+ .password(userDTO.getPassword())
+ .userName(userDTO.getUserName())
+ .roles(userDTO.getRoles())
+ .address(userDTO.getAddress())
+ .age(userDTO.getAge())
+ .phoneNumber(userDTO.getPhoneNumber())
+ .gender(userDTO.getGender())
+ .build();
+ if(userDTO.getId() != null){
+ user.setId(new ObjectId(userDTO.getId()));
+ }
+ return user;
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/mappers/EntityDTOMapper.java b/src/main/java/ro/ubb/istudent/mappers/EntityDTOMapper.java
new file mode 100755
index 0000000..9b38148
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/mappers/EntityDTOMapper.java
@@ -0,0 +1,24 @@
+package ro.ubb.istudent.mappers;
+
+import org.springframework.stereotype.Component;
+import ro.ubb.istudent.domain.User;
+import ro.ubb.istudent.dto.UserDTO;
+
+@Component
+public class EntityDTOMapper {
+
+ public UserDTO toUserDTO(User user){
+ if (user == null) return null;
+ return UserDTO.builder()
+ .id(user.getId().toHexString())
+ .email(user.getEmail())
+ .userName(user.getUserName())
+ .roles(user.getRoles())
+ .address(user.getAddress())
+ .age(user.getAge())
+ .phoneNumber(user.getPhoneNumber())
+ .gender(user.getGender())
+ .build();
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/repository/UserRepository.java b/src/main/java/ro/ubb/istudent/repository/UserRepository.java
new file mode 100755
index 0000000..b3ea9ae
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/repository/UserRepository.java
@@ -0,0 +1,19 @@
+package ro.ubb.istudent.repository;
+
+import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+import ro.ubb.istudent.domain.User;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends MongoRepository {
+
+ Optional findUserById(String id);
+
+ Optional findUserByEmail(String email);
+
+ Optional findUserByUserName(String userName);
+
+}
diff --git a/src/main/java/ro/ubb/istudent/repository/ValidTokenRepository.java b/src/main/java/ro/ubb/istudent/repository/ValidTokenRepository.java
new file mode 100755
index 0000000..cae2335
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/repository/ValidTokenRepository.java
@@ -0,0 +1,17 @@
+package ro.ubb.istudent.repository;
+
+import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import ro.ubb.istudent.domain.ValidToken;
+
+import java.util.Date;
+
+public interface ValidTokenRepository extends MongoRepository {
+
+ void deleteByToken(String token);
+
+ void deleteByExpirationDateBefore(Date expirationDate);
+
+ ValidToken findByTokenAndExpirationDateAfter(String token, Date expirationDate);
+
+}
diff --git a/src/main/java/ro/ubb/istudent/security/AuthenticationTokenFilter.java b/src/main/java/ro/ubb/istudent/security/AuthenticationTokenFilter.java
new file mode 100755
index 0000000..1048264
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/security/AuthenticationTokenFilter.java
@@ -0,0 +1,50 @@
+package ro.ubb.istudent.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import ro.ubb.istudent.config.SecurityTokenProperties;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
+
+ @Autowired
+ private SecurityTokenProperties tokenProperties;
+ @Autowired
+ private TokenUtils tokenUtils;
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ String authToken = httpRequest.getHeader(this.tokenProperties.getHeader());
+ String username = this.tokenUtils.getUsernameFromToken(authToken);
+
+ if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
+ if (this.tokenUtils.validateToken(authToken, userDetails)) {
+ UsernamePasswordAuthenticationToken authentication =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+
+ chain.doFilter(request, response);
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/security/EntryPointUnauthorizedHandler.java b/src/main/java/ro/ubb/istudent/security/EntryPointUnauthorizedHandler.java
new file mode 100755
index 0000000..86b62ac
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/security/EntryPointUnauthorizedHandler.java
@@ -0,0 +1,24 @@
+package ro.ubb.istudent.security;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static ro.ubb.istudent.util.StringUtils.ACCESS_DENIED;
+
+@Component
+public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {
+
+ @Override
+ public void commence(HttpServletRequest httpServletRequest,
+ HttpServletResponse httpServletResponse,
+ AuthenticationException e) throws IOException, ServletException {
+ httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, ACCESS_DENIED);
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/security/TokenUtils.java b/src/main/java/ro/ubb/istudent/security/TokenUtils.java
new file mode 100755
index 0000000..0e57de6
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/security/TokenUtils.java
@@ -0,0 +1,195 @@
+package ro.ubb.istudent.security;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+import ro.ubb.istudent.config.SecurityTokenProperties;
+import ro.ubb.istudent.dto.SecurityUser;
+import ro.ubb.istudent.repository.ValidTokenRepository;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import static ro.ubb.istudent.util.StringUtils.CREATED;
+import static ro.ubb.istudent.util.StringUtils.SUB;
+
+@Component
+public class TokenUtils {
+
+ private final Logger logger = Logger.getLogger(this.getClass());
+
+ private final SecurityTokenProperties tokenProperties;
+ private ValidTokenRepository validTokenRepository;
+
+ @Autowired
+ public TokenUtils(SecurityTokenProperties tokenProperties, ValidTokenRepository validTokenRepository) {
+ this.tokenProperties = tokenProperties;
+ this.validTokenRepository = validTokenRepository;
+ }
+
+ /**
+ * Generate a new token for the given userDetails
+ *
+ * @param userDetails the user details to generate the token for
+ * @return the token generated
+ */
+ public String generateToken(UserDetails userDetails) {
+ Map claims = new HashMap<>();
+ claims.put(SUB, userDetails.getUsername());
+ claims.put(CREATED, this.generateCurrentDate());
+ return this.generateToken(claims);
+ }
+
+ /**
+ * Verify if the given token is a valid token of the user details given
+ *
+ * @param token the token to be verified
+ * @param userDetails the user details
+ * @return true if the token is valid, false otherwise
+ */
+ public boolean validateToken(String token, UserDetails userDetails) {
+ SecurityUser user = (SecurityUser) userDetails;
+ final String username = this.getUsernameFromToken(token);
+ final Date created = this.getCreatedDateFromToken(token);
+ boolean tokenIsValid = validTokenRepository.findByTokenAndExpirationDateAfter(
+ token, new Date()) != null;
+
+ return (username.equals(user.getUsername())
+ && !(this.isTokenExpired(token)) && tokenIsValid);
+ }
+
+
+ /**
+ * Read the username form the given token
+ *
+ * @param token the token to read the username from
+ * @return the username read
+ */
+ public String getUsernameFromToken(String token) {
+ String username;
+ try {
+ final Claims claims = this.getClaimsFromToken(token);
+ username = claims.getSubject();
+ } catch (Exception e) {
+ username = null;
+ }
+ return username;
+ }
+
+ /**
+ * Read created date property from a given token
+ *
+ * @param token to read from
+ * @return the created date
+ */
+ public Date getCreatedDateFromToken(String token) {
+ Date created;
+ try {
+ final Claims claims = this.getClaimsFromToken(token);
+ created = new Date((Long) claims.get("created"));
+ } catch (Exception e) {
+ logger.error(e.toString());
+ created = null;
+ }
+ return created;
+ }
+
+ /**
+ * Read expiration date property from a given token
+ *
+ * @param token to read from
+ * @return the expiration date
+ */
+ public Date getExpirationDateFromToken(String token) {
+ Date expiration;
+ try {
+ final Claims claims = this.getClaimsFromToken(token);
+ expiration = claims.getExpiration();
+ } catch (Exception e) {
+ logger.error(e.toString());
+ expiration = null;
+ }
+ return expiration;
+ }
+
+ /**
+ * Read the claims from a token
+ *
+ * @param token to read form
+ * @return the Claims object read
+ */
+ private Claims getClaimsFromToken(String token) {
+ Claims claims;
+ try {
+ claims = Jwts.parser()
+ .setSigningKey(tokenProperties.getSecretKey())
+ .parseClaimsJws(token)
+ .getBody();
+ } catch (Exception e) {
+ logger.error(e.toString());
+ claims = null;
+ }
+ return claims;
+ }
+
+ /**
+ * Generate the current date
+ *
+ * @return the date generated
+ */
+ private Date generateCurrentDate() {
+ return new Date(System.currentTimeMillis());
+ }
+
+ /**
+ * Generate the expiration date relative to the current date
+ *
+ * @return the date generated
+ */
+ private Date generateExpirationDate() {
+ return new Date(System.currentTimeMillis() + tokenProperties.getExpiration() * 1000);
+ }
+
+ /**
+ * Verify if the token given is expired
+ *
+ * @param token the token to be verified
+ * @return true if the token is expired, false otherwise
+ */
+ private Boolean isTokenExpired(String token) {
+ final Date expiration = this.getExpirationDateFromToken(token);
+ return expiration.before(this.generateCurrentDate());
+ }
+
+
+ /**
+ * Verify if the creation date is before the given lastPasswordReset date
+ *
+ * @param created the token creation date
+ * @param lastPasswordReset the last date of password reset for a user
+ * @return true if the token is created before the last password reset, false otherwise
+ */
+ private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
+ return (lastPasswordReset != null && created.before(lastPasswordReset));
+ }
+
+ /**
+ * Generate a new token for the claims given
+ *
+ * @param claims the claims to generate the token for
+ * @return the token generated
+ */
+ private String generateToken(Map claims) {
+ return Jwts.builder()
+ .setClaims(claims)
+ .setExpiration(this.generateExpirationDate())
+ .signWith(SignatureAlgorithm.HS512, tokenProperties.getSecretKey())
+ .compact();
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/service/ScheduledJobs.java b/src/main/java/ro/ubb/istudent/service/ScheduledJobs.java
new file mode 100755
index 0000000..0c5aa6a
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/service/ScheduledJobs.java
@@ -0,0 +1,27 @@
+package ro.ubb.istudent.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import ro.ubb.istudent.repository.ValidTokenRepository;
+
+import java.util.Date;
+
+@Component
+public class ScheduledJobs {
+
+ private final ValidTokenRepository validTokenRepository;
+
+ @Autowired
+ public ScheduledJobs(ValidTokenRepository validTokenRepository) {
+ this.validTokenRepository = validTokenRepository;
+ }
+
+ @Scheduled(cron = "0 */30 * * * ?")
+ @Transactional
+ public void cleanUpTokenDB() {
+ validTokenRepository.deleteByExpirationDateBefore(new Date());
+ }
+
+}
diff --git a/src/main/java/ro/ubb/istudent/service/UserDetailsService.java b/src/main/java/ro/ubb/istudent/service/UserDetailsService.java
new file mode 100755
index 0000000..72ee849
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/service/UserDetailsService.java
@@ -0,0 +1,52 @@
+package ro.ubb.istudent.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import ro.ubb.istudent.domain.User;
+import ro.ubb.istudent.dto.SecurityUser;
+import ro.ubb.istudent.repository.UserRepository;
+
+import java.util.Optional;
+
+import static ro.ubb.istudent.util.StringUtils.EMPTY_SPACE;
+import static ro.ubb.istudent.util.StringUtils.NOT_FOUND;
+
+@Service
+public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ @Autowired
+ public UserDetailsService(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ public SecurityUser loadUserByUsername(String username) throws UsernameNotFoundException {
+ Optional userByEmail = userRepository.findUserByEmail(username);
+ Optional userByUserName = userRepository.findUserByUserName(username);
+
+ if (userByEmail.isPresent()){
+ User user = userByEmail.get();
+ return SecurityUser.builder()
+ .id(user.getId().toHexString())
+ .roles(user.getRoles())
+ .email(user.getEmail())
+ .password(user.getPassword())
+ .build();
+ }
+
+ if (userByUserName.isPresent()){
+ User user = userByUserName.get();
+ return SecurityUser.builder()
+ .id(user.getId().toHexString())
+ .roles(user.getRoles())
+ .username(user.getUserName())
+ .password(user.getPassword())
+ .build();
+ }
+
+ throw new UsernameNotFoundException(username + EMPTY_SPACE + NOT_FOUND);
+ }
+}
diff --git a/src/main/java/ro/ubb/istudent/service/UserService.java b/src/main/java/ro/ubb/istudent/service/UserService.java
new file mode 100755
index 0000000..1d56724
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/service/UserService.java
@@ -0,0 +1,13 @@
+package ro.ubb.istudent.service;
+
+import ro.ubb.istudent.dto.UserDTO;
+
+public interface UserService {
+
+ boolean saveUser(UserDTO user) throws Throwable;
+
+ UserDTO findByEmail(String email);
+
+ UserDTO findByUserName(String userName);
+
+}
diff --git a/src/main/java/ro/ubb/istudent/service/UserServiceImpl.java b/src/main/java/ro/ubb/istudent/service/UserServiceImpl.java
new file mode 100755
index 0000000..93e856c
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/service/UserServiceImpl.java
@@ -0,0 +1,56 @@
+package ro.ubb.istudent.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import ro.ubb.istudent.domain.User;
+import ro.ubb.istudent.dto.UserDTO;
+import ro.ubb.istudent.mappers.DTOToEntityMapper;
+import ro.ubb.istudent.mappers.EntityDTOMapper;
+import ro.ubb.istudent.repository.UserRepository;
+
+import java.util.Optional;
+
+@Service
+public class UserServiceImpl implements UserService {
+
+ private final UserRepository userRepository;
+ private final DTOToEntityMapper dtoToEntityMapper;
+ private final EntityDTOMapper entityDTOMapper;
+ private final BCryptPasswordEncoder bCryptPasswordEncoder;
+
+ @Autowired
+ public UserServiceImpl(UserRepository userRepository, DTOToEntityMapper dtoToEntityMapper,
+ EntityDTOMapper entityDTOMapper, BCryptPasswordEncoder bCryptPasswordEncoder) {
+ this.userRepository = userRepository;
+ this.dtoToEntityMapper = dtoToEntityMapper;
+ this.entityDTOMapper = entityDTOMapper;
+ this.bCryptPasswordEncoder = bCryptPasswordEncoder;
+ }
+
+ @Override
+ public boolean saveUser(UserDTO userDTO) throws Throwable {
+ User user = dtoToEntityMapper.toUser(userDTO);
+ String password = bCryptPasswordEncoder.encode(user.getPassword());
+ user.setPassword(password);
+ return userRepository.save(user) != null;
+ }
+
+ @Override
+ public UserDTO findByEmail(String email) {
+ Optional userOptional = userRepository.findUserByEmail(email);
+ User user = userOptional.orElseThrow(() -> new IllegalArgumentException("Wrong email"));
+ return entityDTOMapper.toUserDTO(user);
+ }
+
+ @Override
+ public UserDTO findByUserName(String userName) {
+ Optional userOptional = userRepository.findUserByUserName(userName);
+ User user = userOptional.orElseThrow(() -> new IllegalArgumentException("Wrong userName"));
+ return entityDTOMapper.toUserDTO(user);
+ }
+
+}
+
+
+
diff --git a/src/main/java/ro/ubb/istudent/util/HttpHeaders.java b/src/main/java/ro/ubb/istudent/util/HttpHeaders.java
new file mode 100755
index 0000000..714415e
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/util/HttpHeaders.java
@@ -0,0 +1,7 @@
+package ro.ubb.istudent.util;
+
+public class HttpHeaders {
+
+ public static final String AUTH_TOKEN = "X-Auth-Token";
+
+}
diff --git a/src/main/java/ro/ubb/istudent/util/StringUtils.java b/src/main/java/ro/ubb/istudent/util/StringUtils.java
new file mode 100755
index 0000000..2063b48
--- /dev/null
+++ b/src/main/java/ro/ubb/istudent/util/StringUtils.java
@@ -0,0 +1,11 @@
+package ro.ubb.istudent.util;
+
+public class StringUtils {
+
+ public static final String NOT_FOUND = "not found";
+ public static final String SUB = "sub";
+ public static final String CREATED = "created";
+ public static final String EMPTY_SPACE = " ";
+ public static final String ACCESS_DENIED = "Access Denied";
+
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index f5d4db0..6a585d4 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,2 +1,14 @@
application:
- base-url: "http://localhost:8080/api"
\ No newline at end of file
+ base-url: "http://localhost:8080/api"
+
+security:
+ token:
+ secretKey: "superSecretKey"
+ header: "X-Auth-Token"
+ expiration: 3600
+
+
+spring:
+ thymeleaf:
+ mode: LEGACYHTML5
+ cache: false
\ No newline at end of file
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index cc09721..c0d2d22 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -1,50 +1,109 @@
-
-
+
- Spring Boot - POST-GET AJAX Example
-
-
-
+
+ Sign-Up/Login Form
+
+
+
+
+
+
+