From d9aaffca0d10ac9fb5f562c2779dfcc7b9376569 Mon Sep 17 00:00:00 2001 From: Sarah176 <120200645+Sarah176@users.noreply.github.com> Date: Fri, 17 May 2024 20:37:12 +0300 Subject: [PATCH 1/4] Update .gitignore --- .gitignore | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.gitignore b/.gitignore index f98b419fc..8b7d5ec12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,30 @@ .idea/ /starter_code/target/ + +## removed target/ in order to deploy from docker image + +### STS ### + +.apt_generated + +.classpath + +.factorypath + +.project + +.settings + +.springBeans + +.sts4-cache + +### IntelliJ IDEA ### + +.idea + +*.iws + +*.iml + +*.ipr From 7c4039eda46d9b77ebae5182b2f84d8751de6250 Mon Sep 17 00:00:00 2001 From: Sarah176 <120200645+Sarah176@users.noreply.github.com> Date: Fri, 17 May 2024 23:29:26 +0300 Subject: [PATCH 2/4] Update create user --- starter_code/pom.xml | 20 +++++++++++++++++++ .../com/example/demo/SareetaApplication.java | 9 ++++++++- .../demo/controllers/UserController.java | 12 +++++++++++ .../example/demo/model/persistence/User.java | 8 ++++++++ .../model/requests/CreateUserRequest.java | 7 +++++++ 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/starter_code/pom.xml b/starter_code/pom.xml index 608bb212f..77c64bc58 100644 --- a/starter_code/pom.xml +++ b/starter_code/pom.xml @@ -50,6 +50,26 @@ tomcat-maven-plugin 1.1 + + org.springframework.boot + spring-boot-starter-security + + + com.auth0 + java-jwt + 3.10.3 + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + ${lombok.version} + provided + diff --git a/starter_code/src/main/java/com/example/demo/SareetaApplication.java b/starter_code/src/main/java/com/example/demo/SareetaApplication.java index f161f699d..7b5595b1e 100644 --- a/starter_code/src/main/java/com/example/demo/SareetaApplication.java +++ b/starter_code/src/main/java/com/example/demo/SareetaApplication.java @@ -3,12 +3,19 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @EnableJpaRepositories("com.example.demo.model.persistence.repositories") @EntityScan("com.example.demo.model.persistence") -@SpringBootApplication +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) public class SareetaApplication { + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder(){ + return new BCryptPasswordEncoder(); + } public static void main(String[] args) { SpringApplication.run(SareetaApplication.class, args); diff --git a/starter_code/src/main/java/com/example/demo/controllers/UserController.java b/starter_code/src/main/java/com/example/demo/controllers/UserController.java index 87e8089df..8d011144f 100644 --- a/starter_code/src/main/java/com/example/demo/controllers/UserController.java +++ b/starter_code/src/main/java/com/example/demo/controllers/UserController.java @@ -2,9 +2,12 @@ import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -20,6 +23,8 @@ @RestController @RequestMapping("/api/user") +@RequiredArgsConstructor +@Slf4j public class UserController { @Autowired @@ -27,6 +32,7 @@ public class UserController { @Autowired private CartRepository cartRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; @GetMapping("/id/{id}") public ResponseEntity findById(@PathVariable Long id) { @@ -46,6 +52,12 @@ public ResponseEntity createUser(@RequestBody CreateUserRequest createUser Cart cart = new Cart(); cartRepository.save(cart); user.setCart(cart); + if(createUserRequest.getPassword().length()<7 || + !createUserRequest.getPassword().equals(createUserRequest.getConfirmPassword())){ + log.error("Error - Either length is less than 7 or pass and conf pass do not match. Unable to create {}",createUserRequest.getUsername()); + return ResponseEntity.badRequest().build(); + } + user.setPassword(bCryptPasswordEncoder.encode(createUserRequest.getPassword())); userRepository.save(user); return ResponseEntity.ok(user); } diff --git a/starter_code/src/main/java/com/example/demo/model/persistence/User.java b/starter_code/src/main/java/com/example/demo/model/persistence/User.java index ab85ccc60..b8d8aee22 100644 --- a/starter_code/src/main/java/com/example/demo/model/persistence/User.java +++ b/starter_code/src/main/java/com/example/demo/model/persistence/User.java @@ -12,10 +12,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; @Entity @Table(name = "user") +@Setter +@Getter public class User { @Id @@ -26,6 +30,10 @@ public class User { @Column(nullable = false, unique = true) @JsonProperty private String username; + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + @Column(nullable = false) + private String password; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "cart_id", referencedColumnName = "id") diff --git a/starter_code/src/main/java/com/example/demo/model/requests/CreateUserRequest.java b/starter_code/src/main/java/com/example/demo/model/requests/CreateUserRequest.java index a92d0bbb6..eff138c66 100644 --- a/starter_code/src/main/java/com/example/demo/model/requests/CreateUserRequest.java +++ b/starter_code/src/main/java/com/example/demo/model/requests/CreateUserRequest.java @@ -1,11 +1,18 @@ package com.example.demo.model.requests; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +@Data public class CreateUserRequest { @JsonProperty private String username; + @JsonProperty + private String password; + + @JsonProperty + private String confirmPassword; public String getUsername() { return username; From 136454ae207d5303602cadfbddb2e224a3b4c08e Mon Sep 17 00:00:00 2001 From: Sarah176 <120200645+Sarah176@users.noreply.github.com> Date: Fri, 17 May 2024 23:33:07 +0300 Subject: [PATCH 3/4] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8b7d5ec12..1e7380961 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .idea/ /starter_code/target/ - +*/target/** ## removed target/ in order to deploy from docker image ### STS ### From a5db5cdccb124d223fc253320b18b8c5457f3640 Mon Sep 17 00:00:00 2001 From: Sarah176 <120200645+Sarah176@users.noreply.github.com> Date: Fri, 17 May 2024 23:42:32 +0300 Subject: [PATCH 4/4] add security package --- .../security/JWTAuthenticationFilter.java | 61 +++++++++++++++++++ .../JWTAuthenticationVerficationFilter.java | 58 ++++++++++++++++++ .../demo/security/SecurityConstants.java | 10 +++ .../demo/security/UserDetailsServiceImpl.java | 28 +++++++++ .../security/WebSecurityConfiguration.java | 48 +++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 starter_code/src/main/java/com/example/demo/security/JWTAuthenticationFilter.java create mode 100644 starter_code/src/main/java/com/example/demo/security/JWTAuthenticationVerficationFilter.java create mode 100644 starter_code/src/main/java/com/example/demo/security/SecurityConstants.java create mode 100644 starter_code/src/main/java/com/example/demo/security/UserDetailsServiceImpl.java create mode 100644 starter_code/src/main/java/com/example/demo/security/WebSecurityConfiguration.java diff --git a/starter_code/src/main/java/com/example/demo/security/JWTAuthenticationFilter.java b/starter_code/src/main/java/com/example/demo/security/JWTAuthenticationFilter.java new file mode 100644 index 000000000..cae76cee3 --- /dev/null +++ b/starter_code/src/main/java/com/example/demo/security/JWTAuthenticationFilter.java @@ -0,0 +1,61 @@ +package com.example.demo.security; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.web.authentication.UsernamePasswordAuthenticationFilter; + +import com.auth0.jwt.JWT; +import com.example.demo.model.persistence.User; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static com.auth0.jwt.algorithms.Algorithm.HMAC512; + +public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + private AuthenticationManager authenticationManager; + + public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest req, + HttpServletResponse res) throws AuthenticationException { + try { + User credentials = new ObjectMapper() + .readValue(req.getInputStream(), User.class); + + return authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + credentials.getUsername(), + credentials.getPassword(), + new ArrayList<>())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void successfulAuthentication(HttpServletRequest req, + HttpServletResponse res, + FilterChain chain, + Authentication auth) throws IOException, ServletException { + + String token = JWT.create() + .withSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername()) + .withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME)) + .sign(HMAC512(SecurityConstants.SECRET.getBytes())); + res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token); + } +} diff --git a/starter_code/src/main/java/com/example/demo/security/JWTAuthenticationVerficationFilter.java b/starter_code/src/main/java/com/example/demo/security/JWTAuthenticationVerficationFilter.java new file mode 100644 index 000000000..131b625f1 --- /dev/null +++ b/starter_code/src/main/java/com/example/demo/security/JWTAuthenticationVerficationFilter.java @@ -0,0 +1,58 @@ +package com.example.demo.security; + +import java.io.IOException; +import java.util.ArrayList; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.stereotype.Component; + +import com.auth0.jwt.JWT; + +import static com.auth0.jwt.algorithms.Algorithm.HMAC512; + +@Component +public class JWTAuthenticationVerficationFilter extends BasicAuthenticationFilter { + + public JWTAuthenticationVerficationFilter(AuthenticationManager authManager) { + super(authManager); + } + + @Override + protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) + throws IOException, ServletException { + String header = req.getHeader(SecurityConstants.HEADER_STRING); + + if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) { + chain.doFilter(req, res); + return; + } + + UsernamePasswordAuthenticationToken authentication = getAuthentication(req); + + SecurityContextHolder.getContext().setAuthentication(authentication); + chain.doFilter(req, res); + } + + private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req) { + String token = req.getHeader(SecurityConstants.HEADER_STRING); + if (token != null) { + String user = JWT.require(HMAC512(SecurityConstants.SECRET.getBytes())).build() + .verify(token.replace(SecurityConstants.TOKEN_PREFIX, "")) + .getSubject(); + if (user != null) { + return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); + } + return null; + } + return null; + } + +} \ No newline at end of file diff --git a/starter_code/src/main/java/com/example/demo/security/SecurityConstants.java b/starter_code/src/main/java/com/example/demo/security/SecurityConstants.java new file mode 100644 index 000000000..16db3794c --- /dev/null +++ b/starter_code/src/main/java/com/example/demo/security/SecurityConstants.java @@ -0,0 +1,10 @@ +package com.example.demo.security; + +public class SecurityConstants { + + public static final String SECRET = "oursecretkey"; + public static final long EXPIRATION_TIME = 864_000_000; // 10 days + public static final String TOKEN_PREFIX = "Bearer "; + public static final String HEADER_STRING = "Authorization"; + public static final String SIGN_UP_URL = "/api/user/create"; +} \ No newline at end of file diff --git a/starter_code/src/main/java/com/example/demo/security/UserDetailsServiceImpl.java b/starter_code/src/main/java/com/example/demo/security/UserDetailsServiceImpl.java new file mode 100644 index 000000000..4def95b68 --- /dev/null +++ b/starter_code/src/main/java/com/example/demo/security/UserDetailsServiceImpl.java @@ -0,0 +1,28 @@ +package com.example.demo.security; + +import java.util.Collections; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import com.example.demo.model.persistence.User; +import com.example.demo.model.persistence.repositories.UserRepository; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException(username); + } + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.emptyList()); + } +} \ No newline at end of file diff --git a/starter_code/src/main/java/com/example/demo/security/WebSecurityConfiguration.java b/starter_code/src/main/java/com/example/demo/security/WebSecurityConfiguration.java new file mode 100644 index 000000000..ded486a1b --- /dev/null +++ b/starter_code/src/main/java/com/example/demo/security/WebSecurityConfiguration.java @@ -0,0 +1,48 @@ +package com.example.demo.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.crypto.bcrypt.BCryptPasswordEncoder; + +@EnableWebSecurity +public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { + + private UserDetailsServiceImpl userDetailsService; + private BCryptPasswordEncoder bCryptPasswordEncoder; + + public WebSecurityConfiguration(UserDetailsServiceImpl userDetailsService, + BCryptPasswordEncoder bCryptPasswordEncoder) { + this.userDetailsService = userDetailsService; + this.bCryptPasswordEncoder = bCryptPasswordEncoder; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.cors().and().csrf().disable().authorizeRequests() + .antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll() + .anyRequest().authenticated() + .and() + .addFilter(new JWTAuthenticationFilter(authenticationManager())) + .addFilter(new JWTAuthenticationVerficationFilter(authenticationManager())) + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.parentAuthenticationManager(authenticationManagerBean()) + .userDetailsService(userDetailsService) + .passwordEncoder(bCryptPasswordEncoder); + } +} \ No newline at end of file