added users, jwt AT and RT
This commit is contained in:
parent
ed13d6b914
commit
134ae88bd3
|
|
@ -26,6 +26,13 @@ dependencies {
|
||||||
|
|
||||||
compileOnly("org.projectlombok:lombok:1.18.30")
|
compileOnly("org.projectlombok:lombok:1.18.30")
|
||||||
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||||
|
|
||||||
|
|
||||||
|
implementation ("org.springframework.boot:spring-boot-starter-security")
|
||||||
|
testImplementation("org.springframework.security:spring-security-test")
|
||||||
|
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
|
||||||
|
implementation("io.jsonwebtoken:jjwt-impl:0.12.3")
|
||||||
|
implementation("io.jsonwebtoken:jjwt-jackson:0.12.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.kassaev.notes.config
|
||||||
|
|
||||||
|
import com.kassaev.notes.repository.UserRepository
|
||||||
|
import com.kassaev.notes.service.CustomUserDetailsService
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
|
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.core.userdetails.UserDetailsService
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(JwtProperties::class)
|
||||||
|
class Configuration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun userDetailsService(userRepository: UserRepository): UserDetailsService =
|
||||||
|
CustomUserDetailsService(userRepository)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun encoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun authenticationProvider(userRepository: UserRepository): AuthenticationProvider =
|
||||||
|
DaoAuthenticationProvider()
|
||||||
|
.also {
|
||||||
|
it.setUserDetailsService(userDetailsService(userRepository))
|
||||||
|
it.setPasswordEncoder(encoder())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager =
|
||||||
|
config.authenticationManager
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.kassaev.notes.config
|
||||||
|
|
||||||
|
import com.kassaev.notes.service.CustomUserDetailsService
|
||||||
|
import com.kassaev.notes.service.TokenService
|
||||||
|
import jakarta.servlet.FilterChain
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class JwtAuthenticationFilter(
|
||||||
|
private val userDetailsService: CustomUserDetailsService,
|
||||||
|
private val tokenService: TokenService
|
||||||
|
): OncePerRequestFilter() {
|
||||||
|
override fun doFilterInternal(
|
||||||
|
request: HttpServletRequest,
|
||||||
|
response: HttpServletResponse,
|
||||||
|
filterChain: FilterChain
|
||||||
|
) {
|
||||||
|
val authHeader: String? = request.getHeader("Authorization")
|
||||||
|
|
||||||
|
if(authHeader.doNotContainBearerToken()){
|
||||||
|
filterChain.doFilter(request, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val jwtToken = authHeader!!.extractTokenValue()
|
||||||
|
val email = tokenService.extractEmail(jwtToken)
|
||||||
|
|
||||||
|
if (email != null && SecurityContextHolder.getContext().authentication == null){
|
||||||
|
val foundUser = userDetailsService.loadUserByUsername(email)
|
||||||
|
|
||||||
|
if (tokenService.isValid(jwtToken, foundUser)){
|
||||||
|
updateContext(foundUser, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateContext(foundUser: UserDetails, request: HttpServletRequest) {
|
||||||
|
val authToken = UsernamePasswordAuthenticationToken(foundUser, null, foundUser.authorities)
|
||||||
|
authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().authentication = authToken
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String?.doNotContainBearerToken(): Boolean =
|
||||||
|
this == null || !this.startsWith("Bearer ")
|
||||||
|
|
||||||
|
private fun String.extractTokenValue(): String =
|
||||||
|
this.substringAfter("Bearer ")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.kassaev.notes.config
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
|
||||||
|
@ConfigurationProperties("jwt")
|
||||||
|
data class JwtProperties(
|
||||||
|
val key: String,
|
||||||
|
val accessTokenExpiration: Long,
|
||||||
|
val refreshTokenExpiration: Long
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.kassaev.notes.config
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider
|
||||||
|
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.web.DefaultSecurityFilterChain
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfiguration(
|
||||||
|
private val authenticationProvider: AuthenticationProvider
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(
|
||||||
|
http: HttpSecurity,
|
||||||
|
jwtAuthenticationFilter: JwtAuthenticationFilter
|
||||||
|
): DefaultSecurityFilterChain =
|
||||||
|
http
|
||||||
|
.csrf { it.disable() }
|
||||||
|
.authorizeHttpRequests {
|
||||||
|
it
|
||||||
|
.requestMatchers("/api/v1.0.0/auth", "/api/v1.0.0/auth/refresh", "/error")
|
||||||
|
.permitAll()
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/v1.0.0/user")
|
||||||
|
.permitAll()
|
||||||
|
.requestMatchers("/api/v1.0.0/user**")
|
||||||
|
.hasRole("ADMIN")
|
||||||
|
.anyRequest()
|
||||||
|
.fullyAuthenticated()
|
||||||
|
}
|
||||||
|
.sessionManagement {
|
||||||
|
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
}
|
||||||
|
.authenticationProvider(authenticationProvider)
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.kassaev.notes.controller.auth
|
||||||
|
|
||||||
|
import com.kassaev.notes.service.AuthenticationService
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1.0.0/auth")
|
||||||
|
class AuthController(
|
||||||
|
private val authenticationService: AuthenticationService
|
||||||
|
) {
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
fun authenticate(@RequestBody authRequest: AuthenticationRequest): AuthenticationResponse =
|
||||||
|
authenticationService.authentication(authRequest)
|
||||||
|
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
fun refreshAccessToken(
|
||||||
|
@RequestBody request: RefreshTokenRequest
|
||||||
|
): TokenResponse =
|
||||||
|
authenticationService.refreshAccessToken(request.token)
|
||||||
|
?.mapToTokenResponse()
|
||||||
|
?: throw ResponseStatusException(HttpStatus.FORBIDDEN, "Invalid refresh token!")
|
||||||
|
|
||||||
|
private fun String.mapToTokenResponse(): TokenResponse =
|
||||||
|
TokenResponse(
|
||||||
|
token = this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.kassaev.notes.controller.auth
|
||||||
|
|
||||||
|
data class AuthenticationRequest(
|
||||||
|
val email: String,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.kassaev.notes.controller.auth
|
||||||
|
|
||||||
|
data class AuthenticationResponse(
|
||||||
|
val accessToken: String,
|
||||||
|
val refreshToken: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.kassaev.notes.controller.auth
|
||||||
|
|
||||||
|
data class RefreshTokenRequest(
|
||||||
|
val token: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.kassaev.notes.controller.auth
|
||||||
|
|
||||||
|
data class TokenResponse(
|
||||||
|
val token: String
|
||||||
|
)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.kassaev.notes.controller
|
package com.kassaev.notes.controller.note
|
||||||
|
|
||||||
import com.kassaev.notes.model.Note
|
import com.kassaev.notes.model.Note
|
||||||
import com.kassaev.notes.service.NoteService
|
import com.kassaev.notes.service.NoteService
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.kassaev.notes.controller.user
|
||||||
|
|
||||||
|
import com.kassaev.notes.model.Role
|
||||||
|
import com.kassaev.notes.model.User
|
||||||
|
import com.kassaev.notes.service.UserService
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1.0.0/user")
|
||||||
|
class UserController(
|
||||||
|
private val userService: UserService
|
||||||
|
) {
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
fun create(@RequestBody userRequest: UserRequest): UserResponse =
|
||||||
|
userService.createUser(
|
||||||
|
user = userRequest.toModel()
|
||||||
|
)
|
||||||
|
?.toResponse()
|
||||||
|
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user.")
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun listAll(): List<UserResponse> =
|
||||||
|
userService.findAll()
|
||||||
|
.map { it.toResponse() }
|
||||||
|
|
||||||
|
@GetMapping("/{uuid}")
|
||||||
|
fun findByUUID(@PathVariable uuid: UUID): UserResponse =
|
||||||
|
userService.findByUUID(uuid)
|
||||||
|
?.toResponse()
|
||||||
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Cannot find a user.")
|
||||||
|
|
||||||
|
@DeleteMapping("/{uuid}")
|
||||||
|
fun deleteByUUID(@PathVariable uuid: UUID): ResponseEntity<Boolean> {
|
||||||
|
val success = userService.deleteByUUID(uuid)
|
||||||
|
|
||||||
|
return if(success) {
|
||||||
|
ResponseEntity.noContent().build()
|
||||||
|
} else {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Cannot find a user.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UserRequest.toModel(): User =
|
||||||
|
User(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
email = this.email,
|
||||||
|
password = this.password,
|
||||||
|
role = Role.USER
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun User.toResponse(): UserResponse =
|
||||||
|
UserResponse(
|
||||||
|
uuid = this.id,
|
||||||
|
email = this.email
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.kassaev.notes.controller.user
|
||||||
|
|
||||||
|
data class UserRequest(
|
||||||
|
val email: String,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.kassaev.notes.controller.user
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class UserResponse(
|
||||||
|
val uuid: UUID,
|
||||||
|
val email: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.kassaev.notes.model
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class User(
|
||||||
|
val id: UUID,
|
||||||
|
val email: String,
|
||||||
|
val password: String,
|
||||||
|
val role: Role
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Role{
|
||||||
|
USER, ADMIN
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.kassaev.notes.repository
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class RefreshTokenRepository {
|
||||||
|
|
||||||
|
private val tokens = mutableMapOf<String, UserDetails>()
|
||||||
|
|
||||||
|
fun findUserDetailsByToken(token: String): UserDetails? =
|
||||||
|
tokens[token]
|
||||||
|
|
||||||
|
fun save(token: String, userDetails: UserDetails) {
|
||||||
|
tokens[token] = userDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.kassaev.notes.repository
|
||||||
|
|
||||||
|
import com.kassaev.notes.model.Role
|
||||||
|
import com.kassaev.notes.model.User
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class UserRepository(
|
||||||
|
private val encoder: PasswordEncoder
|
||||||
|
) {
|
||||||
|
private val users = mutableListOf(
|
||||||
|
User(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
email = "user1@mail.com",
|
||||||
|
password = encoder.encode("pass1"),
|
||||||
|
role = Role.USER
|
||||||
|
),
|
||||||
|
User(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
email = "user2@mail.com",
|
||||||
|
password = encoder.encode("pass2"),
|
||||||
|
role = Role.USER
|
||||||
|
),
|
||||||
|
User(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
email = "user3@mail.com",
|
||||||
|
password = encoder.encode("pass3"),
|
||||||
|
role = Role.ADMIN
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun save(user: User): Boolean {
|
||||||
|
val updated = user.copy(password = encoder.encode(
|
||||||
|
user.password
|
||||||
|
))
|
||||||
|
return users.add(updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findByEmail(email: String): User? =
|
||||||
|
users.firstOrNull { it.email == email }
|
||||||
|
|
||||||
|
fun findByUUID(uuid: UUID): User? =
|
||||||
|
users.firstOrNull { it.id == uuid }
|
||||||
|
|
||||||
|
fun findAll(): List<User> =
|
||||||
|
users
|
||||||
|
|
||||||
|
fun deleteByUUID(uuid: UUID): Boolean {
|
||||||
|
val foundUser = findByUUID(uuid)
|
||||||
|
|
||||||
|
return foundUser?.let {
|
||||||
|
users.remove(it)
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.kassaev.notes.service
|
||||||
|
|
||||||
|
import com.kassaev.notes.config.JwtProperties
|
||||||
|
import com.kassaev.notes.controller.auth.AuthenticationRequest
|
||||||
|
import com.kassaev.notes.controller.auth.AuthenticationResponse
|
||||||
|
import com.kassaev.notes.repository.RefreshTokenRepository
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AuthenticationService(
|
||||||
|
private val authManager: AuthenticationManager,
|
||||||
|
private val userDetailsService: CustomUserDetailsService,
|
||||||
|
private val tokenService: TokenService,
|
||||||
|
private val jwtProperties: JwtProperties,
|
||||||
|
private val refreshTokenRepository: RefreshTokenRepository
|
||||||
|
) {
|
||||||
|
fun authentication(authRequest: AuthenticationRequest): AuthenticationResponse {
|
||||||
|
authManager.authenticate(
|
||||||
|
UsernamePasswordAuthenticationToken(
|
||||||
|
authRequest.email,
|
||||||
|
authRequest.password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val user = userDetailsService.loadUserByUsername(authRequest.email)
|
||||||
|
|
||||||
|
val accessToken = generateAccessToken(user)
|
||||||
|
val refreshToken = generateRefreshToken(user)
|
||||||
|
|
||||||
|
refreshTokenRepository.save(refreshToken, user)
|
||||||
|
|
||||||
|
return AuthenticationResponse(
|
||||||
|
accessToken = accessToken,
|
||||||
|
refreshToken = refreshToken
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateRefreshToken(user: UserDetails) = tokenService.generate(
|
||||||
|
userDetails = user,
|
||||||
|
expirationDate = Date(System.currentTimeMillis() + jwtProperties.refreshTokenExpiration)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun generateAccessToken(user: UserDetails) = tokenService.generate(
|
||||||
|
userDetails = user,
|
||||||
|
expirationDate = Date(System.currentTimeMillis() + jwtProperties.accessTokenExpiration)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun refreshAccessToken(token: String): String? {
|
||||||
|
val extractedEmail = tokenService.extractEmail(token)
|
||||||
|
|
||||||
|
return extractedEmail?.let { email ->
|
||||||
|
val currentUserDetails = userDetailsService.loadUserByUsername(email)
|
||||||
|
val refreshTokenUserDetails = refreshTokenRepository.findUserDetailsByToken(token)
|
||||||
|
|
||||||
|
if (!tokenService.isExpired(token) && currentUserDetails.username == refreshTokenUserDetails?.username)
|
||||||
|
// TODO: check if this refresh token is the same token in db for this user <refresh token, user>. If so generate new access token and refresh token and update record in with newly created refresh token for this user.
|
||||||
|
generateAccessToken(currentUserDetails)
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.kassaev.notes.service
|
||||||
|
|
||||||
|
import com.kassaev.notes.repository.UserRepository
|
||||||
|
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.core.userdetails.UsernameNotFoundException
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
typealias ApplicationUser = com.kassaev.notes.model.User
|
||||||
|
@Service
|
||||||
|
class CustomUserDetailsService(
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
): UserDetailsService {
|
||||||
|
override fun loadUserByUsername(username: String): UserDetails =
|
||||||
|
userRepository.findByEmail(username)
|
||||||
|
?.mapToUserDetails()
|
||||||
|
?: throw UsernameNotFoundException("Not found!")
|
||||||
|
|
||||||
|
private fun ApplicationUser.mapToUserDetails(): UserDetails =
|
||||||
|
User.builder()
|
||||||
|
.username(this.email)
|
||||||
|
.password(this.password)
|
||||||
|
.roles(this.role.name)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.kassaev.notes.service
|
||||||
|
|
||||||
|
import com.kassaev.notes.config.JwtProperties
|
||||||
|
import io.jsonwebtoken.Claims
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class TokenService(
|
||||||
|
jwtProperties: JwtProperties
|
||||||
|
) {
|
||||||
|
private val secretKey = Keys.hmacShaKeyFor(
|
||||||
|
jwtProperties.key.toByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun generate(
|
||||||
|
userDetails: UserDetails,
|
||||||
|
expirationDate: Date,
|
||||||
|
additionalClaims: Map<String, Any> = emptyMap()
|
||||||
|
): String =
|
||||||
|
Jwts.builder()
|
||||||
|
.claims()
|
||||||
|
.subject(userDetails.username)
|
||||||
|
.issuedAt(Date(System.currentTimeMillis()))
|
||||||
|
.expiration(expirationDate)
|
||||||
|
.add(additionalClaims)
|
||||||
|
.and()
|
||||||
|
.signWith(secretKey)
|
||||||
|
.compact()
|
||||||
|
|
||||||
|
fun extractEmail(token: String): String? =
|
||||||
|
getAllClaims(token)
|
||||||
|
.subject
|
||||||
|
|
||||||
|
fun isExpired(token: String): Boolean =
|
||||||
|
getAllClaims(token)
|
||||||
|
.expiration
|
||||||
|
.before(Date(System.currentTimeMillis()))
|
||||||
|
|
||||||
|
fun isValid(token: String, userDetails: UserDetails): Boolean {
|
||||||
|
val email = extractEmail(token)
|
||||||
|
|
||||||
|
return userDetails.username == email && !isExpired(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAllClaims(token: String): Claims {
|
||||||
|
val parser = Jwts.parser()
|
||||||
|
.verifyWith(secretKey)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return parser
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.kassaev.notes.service
|
||||||
|
|
||||||
|
import com.kassaev.notes.model.User
|
||||||
|
import com.kassaev.notes.repository.UserRepository
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class UserService(
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
) {
|
||||||
|
fun createUser(user: User): User? {
|
||||||
|
val found = userRepository.findByEmail(user.email)
|
||||||
|
|
||||||
|
return if (found == null) {
|
||||||
|
userRepository.save(user)
|
||||||
|
user
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findByUUID(uuid: UUID): User? =
|
||||||
|
userRepository.findByUUID(uuid)
|
||||||
|
|
||||||
|
fun findAll(): List<User> =
|
||||||
|
userRepository.findAll()
|
||||||
|
|
||||||
|
fun deleteByUUID(uuid: UUID): Boolean =
|
||||||
|
userRepository.deleteByUUID(uuid)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
jwt:
|
||||||
|
key: ${JWT_KEY}
|
||||||
|
access-token-expiration: 3600000
|
||||||
|
refresh-token-expiration: 86400000
|
||||||
Loading…
Reference in New Issue