diff --git a/pom.xml b/pom.xml index 163ad53..f580e41 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,40 @@ org.jetbrains.kotlin kotlin-stdlib - + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + runtime + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + runtime + 0.11.5 + + + org.springframework.boot + spring-boot-starter-security + + + jakarta.inject + jakarta.inject-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + compile + org.springframework.boot spring-boot-starter-test diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/com/coded/spring/ordering/Application.kt index 8554e49..8707209 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/com/coded/spring/ordering/Application.kt @@ -1,10 +1,36 @@ package com.coded.spring.ordering +import org.springframework.boot.CommandLineRunner import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder @SpringBootApplication -class Application +@ComponentScan("com.example.authentication") + +class Application{ + + + @Bean + fun initUsers(userRepository: UsersRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner { + val user = UserEntity( + name = "HelloUser", + username = "testuser", + password = passwordEncoder.encode("password123"), + age = 18, + role = Roles.USER + ) + if (userRepository.findByUsername(user.username) == null) { + println("Creating user ${user.username}") + userRepository.save(user) + } else { + println("User ${user.username} already exists") + } + } +} fun main(args: Array) { runApplication(*args) diff --git a/src/main/kotlin/com/coded/spring/ordering/HelloWorld.kt b/src/main/kotlin/com/coded/spring/ordering/HelloWorld.kt new file mode 100644 index 0000000..878f828 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/HelloWorld.kt @@ -0,0 +1,13 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class HelloWorld { + + @GetMapping("/Hello") + fun sayingHello(): String{ + return "Hello World ,, najla" + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/OnlineOrdering.kt b/src/main/kotlin/com/coded/spring/ordering/OnlineOrdering.kt new file mode 100644 index 0000000..10b516a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OnlineOrdering.kt @@ -0,0 +1,46 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class OnlineOrdering (val ordersRepositry: OrdersRepositry){ + + + +// var customerOrd://customerOrder("ali","pizza hut", listOf("pizza", "chicken wings" , "pepsi") ) + + var customerOrdList = mutableListOf() + @PostMapping("/order") + fun order(@RequestBody newOrder: customerOrder): String{ + customerOrdList.add(newOrder) + return "your order submitted" + } + + @GetMapping("/order") + fun allOrder (): List{ + return customerOrdList + } + + @PostMapping("/orderDB") + fun orderDB (@RequestBody newOrder: customerOrderDB){ + var theOrder= OrdersEntity() + theOrder.userId = newOrder.userId + theOrder.resturant = newOrder.resturant + theOrder.items = newOrder.items + ordersRepositry.save(theOrder) + + } + + @GetMapping("/orderDB") + fun orderDB ():List{ + var allOrderDB = OrdersEntity() + return ordersRepositry.findAll() + } +} + + +data class customerOrder(var user: String,var resturant: String , var items: List) +data class customerOrderDB(var userId: Int,var resturant: String , var items: String) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/OrdersEntity.kt b/src/main/kotlin/com/coded/spring/ordering/OrdersEntity.kt new file mode 100644 index 0000000..8e548b4 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OrdersEntity.kt @@ -0,0 +1,21 @@ +package com.coded.spring.ordering + +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrdersRepositry : JpaRepository +@Entity +@Table(name = "orders") +data class OrdersEntity ( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Int, + var userId: Int, + var resturant: String, + var items: String + ){ + constructor(): this (0,0,"","") + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/UserEntity.kt b/src/main/kotlin/com/coded/spring/ordering/UserEntity.kt new file mode 100644 index 0000000..257ebd3 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/UserEntity.kt @@ -0,0 +1,29 @@ +package com.coded.spring.ordering + +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface UsersRepository: JpaRepository{ + fun findByUsername(username: String): UserEntity? +} + +@Entity +@Table(name = "users") +data class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Int? = null , + var name: String, + var age: Int, + var username: String, + var password: String, + @Enumerated(EnumType.STRING) + val role: Roles = Roles.USER) { + constructor() : this(0, "name", 0, "username", "password", Roles.USER) +} + +enum class Roles{ + USER,ADMIN +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/authentication/CustomUserDetailsService.kt b/src/main/kotlin/com/example/authentication/CustomUserDetailsService.kt new file mode 100644 index 0000000..9ab0434 --- /dev/null +++ b/src/main/kotlin/com/example/authentication/CustomUserDetailsService.kt @@ -0,0 +1,21 @@ +package com.example.authentication + +import com.coded.spring.ordering.UsersRepository +import org.springframework.security.core.userdetails.* +import org.springframework.stereotype.Service + +@Service +class CustomUserDetailsService( + private val usersRepository: UsersRepository +) : UserDetailsService { + override fun loadUserByUsername(username: String): UserDetails { + val user = usersRepository.findByUsername(username) + ?: throw UsernameNotFoundException("User not found") + + return User.builder() + .username(user.username) + .password(user.password) + .roles(user.role.toString()) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/authentication/SecurityConfig.kt b/src/main/kotlin/com/example/authentication/SecurityConfig.kt new file mode 100644 index 0000000..fc1d32c --- /dev/null +++ b/src/main/kotlin/com/example/authentication/SecurityConfig.kt @@ -0,0 +1,58 @@ +package com.example.authentication + +import com.example.authentication.jwt.JwtAuthenticationFilter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.dao.DaoAuthenticationProvider +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +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.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + + + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val jwtAuthFilter: JwtAuthenticationFilter, + private val userDetailsService: UserDetailsService +) { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.requestMatchers("/auth/**").permitAll() + .anyRequest().authenticated() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + + return http.build() + } + + @Bean + fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() + + @Bean + fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager = + config.authenticationManager + + @Bean + fun authenticationProvider(): AuthenticationProvider { + val provider = DaoAuthenticationProvider() + provider.setUserDetailsService(userDetailsService) + provider.setPasswordEncoder(passwordEncoder()) + return provider + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/authentication/jwt/AuthenticationController.kt b/src/main/kotlin/com/example/authentication/jwt/AuthenticationController.kt new file mode 100644 index 0000000..e96bfb9 --- /dev/null +++ b/src/main/kotlin/com/example/authentication/jwt/AuthenticationController.kt @@ -0,0 +1,39 @@ +package com.example.authentication.jwt + +import org.springframework.security.authentication.* +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.web.bind.annotation.* + + +@RestController +@RequestMapping("/auth") +class AuthenticationController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val jwtService: JwtService +) { + + @PostMapping("/login") + fun login(@RequestBody authRequest: AuthenticationRequest): AuthenticationResponse { + val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password) + val authentication = authenticationManager.authenticate(authToken) + + if (authentication.isAuthenticated) { + val userDetails = userDetailsService.loadUserByUsername(authRequest.username) + val token = jwtService.generateToken(userDetails.username) + return AuthenticationResponse (token) + } else { + throw UsernameNotFoundException("Invalid user request!") + } + } +} + +data class AuthenticationRequest( + val username: String, + val password: String +) + +data class AuthenticationResponse( + val token: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/example/authentication/jwt/JwtAuthenticationFilter.kt b/src/main/kotlin/com/example/authentication/jwt/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..f15819f --- /dev/null +++ b/src/main/kotlin/com/example/authentication/jwt/JwtAuthenticationFilter.kt @@ -0,0 +1,45 @@ +package com.example.authentication.jwt + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.* +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter + +@Component +class JwtAuthenticationFilter( + private val jwtService: JwtService, + private val userDetailsService: UserDetailsService +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val authHeader = request.getHeader("Authorization") + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response) + return + } + + val token = authHeader.substring(7) + val username = jwtService.extractUsername(token) + + if (SecurityContextHolder.getContext().authentication == null) { + if (jwtService.isTokenValid(token, username)) { + val userDetails = userDetailsService.loadUserByUsername(username) + val authToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities + ) + authToken.details = WebAuthenticationDetailsSource().buildDetails(request) + SecurityContextHolder.getContext().authentication = authToken + } + } + + filterChain.doFilter(request, response) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/authentication/jwt/JwtService.kt b/src/main/kotlin/com/example/authentication/jwt/JwtService.kt new file mode 100644 index 0000000..b1888ee --- /dev/null +++ b/src/main/kotlin/com/example/authentication/jwt/JwtService.kt @@ -0,0 +1,42 @@ +package com.example.authentication.jwt + +import io.jsonwebtoken.* +import io.jsonwebtoken.security.Keys +import org.springframework.stereotype.Component +import java.util.* +import javax.crypto.SecretKey + +@Component +class JwtService { + + private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) + private val expirationMs: Long = 1000 * 60 * 60 + + fun generateToken(username: String): String { + val now = Date() + val expiry = Date(now.time + expirationMs) + + return Jwts.builder() + .setSubject(username) + .setIssuedAt(now) + .setExpiration(expiry) + .signWith(secretKey) + .compact() + } + + fun extractUsername(token: String): String = + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .body + .subject + + fun isTokenValid(token: String, username: String): Boolean { + return try { + extractUsername(token) == username + } catch (e: Exception) { + false + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..de82fb4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ spring.application.name=Kotlin.SpringbootV2 +spring.jpa.show-sql= true + +spring.datasource.url=jdbc:postgresql://localhost:5432/ProjectDB +spring.datasource.username=postgres +spring.datasource.password=123456 +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt index b2e2320..ac6f51f 100644 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -2,12 +2,18 @@ package com.coded.spring.ordering import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest +import org.springframework.web.client.RestTemplate +import org.springframework.web.client.getForEntity -@SpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ApplicationTests { + lateinit var restTemplate : RestTemplate + @Test fun contextLoads() { + + } }