Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,18 @@
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>

<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand All @@ -58,6 +69,27 @@
<artifactId>kotlin-test-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
35 changes: 35 additions & 0 deletions src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.coded.spring.ordering

import com.coded.spring.ordering.entities.Roles
import com.coded.spring.ordering.entities.UserEntity
import com.coded.spring.ordering.repositories.UsersRepository

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.security.crypto.password.PasswordEncoder

@SpringBootApplication
class InitUserRunner {
@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<String>) {
runApplication<Application>(*args).close()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.coded.spring.ordering.authentication

import com.coded.spring.ordering.repositories.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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.coded.spring.ordering.authentication


import com.coded.spring.ordering.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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.coded.spring.ordering.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
) {

@GetMapping("/test")
fun testing() = "test"

@PostMapping("/login")
fun login(@RequestBody authRequest: AuthenticationRequest): AuthenticationResponse {
println("🔐 Received login request: $authRequest")

val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password)
println("🔐 Created UsernamePasswordAuthenticationToken")

val authentication = try {
authenticationManager.authenticate(authToken)
} catch (ex: Exception) {
println("❌ Authentication failed: ${ex.message}")
throw UsernameNotFoundException("Invalid credentials")
}

println("✅ Authentication success: ${authentication.isAuthenticated}")
println("➡️ Authorities: ${authentication.authorities}")

if (authentication.isAuthenticated) {
val userDetails = userDetailsService.loadUserByUsername(authRequest.username)
println("✅ Loaded user details for: ${userDetails.username}")

val token = jwtService.generateToken(userDetails.username)
println("🔑 Generated JWT token: $token")

return AuthenticationResponse(token)
} else {
println("❌ Authentication marked as not authenticated")
throw UsernameNotFoundException("Invalid user request!")
}

}
}

data class AuthenticationRequest(
val username: String,
val password: String
)

data class AuthenticationResponse(
val token: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.coded.spring.ordering.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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.coded.spring.ordering.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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.coded.spring.ordering.controllers

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 HelloWorldController(){

@GetMapping("/hello")
fun helloWorld() = "Hello World"

@GetMapping("/boo")
fun testing(): String {
return "AAAA"
}

@PostMapping("/hello")
fun helloName(@RequestBody req: Name) = "Hello ${req.name}!"
}

data class Name(
val name:String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.coded.spring.ordering.controllers

import com.coded.spring.ordering.entities.ItemEntity
import com.coded.spring.ordering.entities.OrderEntity
import com.coded.spring.ordering.repositories.OrdersRepository
import com.coded.spring.ordering.services.OrdersService
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 OrdersController(val ordersRepository: OrdersRepository, val ordersService: OrdersService) {

@PostMapping("/orders")
fun createNewOrder(@RequestBody req: OrderRequest) {
ordersService.createOrder(userId = req.user)
}

@GetMapping("/orders")
fun getOrders(): MutableList<OrderEntity> {
return ordersRepository.findAll()
}


data class OrderRequest(
val user: Long,
val restaurant: String,
val items: List<ItemEntity>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.coded.spring.ordering.controllers

import com.coded.spring.ordering.services.UsersService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class UsersController(
private val usersService: UsersService
){

@GetMapping("/users/v1/list")
fun users() = usersService.listUsers()
}
Loading