Skip to content

Commit

Permalink
Merge pull request #5 from NTF-marketplace/feature/valid-refreshToken
Browse files Browse the repository at this point in the history
feat: reissue accessToken
  • Loading branch information
min-96 authored May 14, 2024
2 parents 2b39e4c + 80f0532 commit 6117d96
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 6 deletions.
26 changes: 25 additions & 1 deletion src/main/kotlin/com/api/auth/config/SecurityConfig.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
package com.api.auth.config

import com.api.auth.security.RefreshTokenValidationFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter


@Configuration
class SecurityConfig {
@EnableReactiveMethodSecurity
class SecurityConfig(
private val refreshTokenValidationFilter: RefreshTokenValidationFilter
) {


@Bean
fun webSecurityCustomizer(): WebSecurityCustomizer {
return WebSecurityCustomizer { web: WebSecurity -> web.ignoring().requestMatchers("/v1/auth") }
}

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() }
.authorizeHttpRequests {
it.requestMatchers("/v1/auth").permitAll()
it.requestMatchers("/v1/auth/reissue").authenticated()
it.anyRequest().authenticated()
}
.addFilterBefore(refreshTokenValidationFilter, UsernamePasswordAuthenticationFilter::class.java)


return http.build()
}
}
19 changes: 18 additions & 1 deletion src/main/kotlin/com/api/auth/controller/AuthController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import com.api.auth.controller.dto.JwtRequest
import com.api.auth.controller.dto.JwtResponse
import com.api.auth.service.AuthService
import com.nimbusds.oauth2.sdk.TokenRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.ReactiveSecurityContextHolder
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
Expand All @@ -17,7 +22,19 @@ class AuthController(
) {

@PostMapping
fun createToken(@RequestBody request: JwtRequest): Mono<JwtResponse> {
fun createToken(@RequestBody request: JwtRequest): Mono<ResponseEntity<JwtResponse>> {
return authService.createToken(request)
.flatMap {
Mono.just(ResponseEntity.ok().body(it))
}
.onErrorResume {
Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null))
}

}

@PostMapping("/reissue")
fun reissueAccessToken(authentication: Authentication): String {
return authService.reissueAccessToken(authentication.principal.toString())
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/com/api/auth/controller/dto/JwtResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package com.api.auth.controller.dto

data class JwtResponse(
val accessToken: String,
val refreshToken: String,
val refreshToken: String?,
)
55 changes: 55 additions & 0 deletions src/main/kotlin/com/api/auth/jwtUtil/JwtProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.api.auth.jwtUtil


import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.crypto.RSASSAVerifier
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import org.springframework.stereotype.Component
import java.util.*

@Component
class JwtProvider(
private val jwtBuilder: JwtBuilder,
private val rsaKeyBuilder: RsaKeyBuilder
) {

fun validateToken(token: String): Boolean {
try {
val rsaKey = rsaKeyBuilder.loadRsaKeyFromFile()
val publicKey = rsaKey.toRSAPublicKey()

val signedJWT = SignedJWT.parse(token)

val verifier = RSASSAVerifier(publicKey)

if (!signedJWT.verify(verifier)) {
return false
}

val claims = signedJWT.jwtClaimsSet
return Date().before(claims.expirationTime)

} catch (e: Exception) {
println("Error during token validation: ${e.message}")
return false
}
}

fun getClaimsFromToken(token: String): JWTClaimsSet? {
try {
val rsaKey = rsaKeyBuilder.loadRsaKeyFromFile()
val publicKey = rsaKey.toRSAPublicKey()

val signedJWT = SignedJWT.parse(token)
val verifier = RSASSAVerifier(publicKey)

if (signedJWT.verify(verifier)) {
return signedJWT.jwtClaimsSet
}
} catch (e: Exception) {
println("Error during token decoding: ${e.message}")
}
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.api.auth.security

import com.api.auth.jwtUtil.JwtProvider
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.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class RefreshTokenValidationFilter(
private val jwtProvider: JwtProvider
): OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val refreshToken = request.getHeader("Authorization")?.substring("Bearer ".length)
if (refreshToken != null && jwtProvider.validateToken(refreshToken)) {
val address = jwtProvider.getClaimsFromToken(refreshToken)?.getClaim("address")
val authentication = UsernamePasswordAuthenticationToken(
address, null, null
)
SecurityContextHolder.getContext().setAuthentication(authentication)
filterChain.doFilter(request, response)
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Refresh Token")
}
}
}
16 changes: 13 additions & 3 deletions src/main/kotlin/com/api/auth/service/AuthService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class AuthService(
@Transactional
fun createToken(request: JwtRequest): Mono<JwtResponse> {
val issuedAt = Instant.now()
val expiresAtAccess = issuedAt.plus(1, ChronoUnit.DAYS)

val accessToken = jwtBuilder.buildJwtToken(issuedAt,expiresAtAccess,request.address)
val accessToken = issueAccessToken(request.address,issuedAt)
return findOrCreate(issuedAt,request.address).map {
JwtResponse(accessToken,it)
}
Expand Down Expand Up @@ -57,4 +55,16 @@ class AuthService(

return refreshTokenRepository.save(refreshToken)
}

private fun issueAccessToken(address: String, issuedAt: Instant): String {
val expiresAtAccess = issuedAt.plus(1, ChronoUnit.DAYS)

return jwtBuilder.buildJwtToken(issuedAt,expiresAtAccess,address)
}

fun reissueAccessToken(address: String): String {
val issuedAt = Instant.now()
return issueAccessToken(address,issuedAt)

}
}
8 changes: 8 additions & 0 deletions src/main/resources/http/auth.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### GET request to example server
GET https://examples.http-client.intellij.net/get
?generated-in=IntelliJ IDEA

###
POST http://localhost:8081/v1/auth/reissue
Content-Type: application/json
Authorization: Bearer eyJraWQiOiIxOWJkMDRmOC1jNGRkLTQwZGYtYTc4NC1iY2Y5NTU5NmU2OTYiLCJhbGciOiJSUzI1NiJ9.eyJuYmYiOjE3MTU2MDg1OTAsImFkZHJlc3MiOiIweDAxYjcyYjRhYTNmNjZmMjEzZDYyZDUzZTgyOWJjMTcyYTZhNzI4NjciLCJleHAiOjE3MTYwNDA1OTAsImlhdCI6MTcxNTYwODU5MH0.MwcA2pAjk5l1M7-5mkYtwPX7ZlB4a6sCSqPsROZ9cwo-7hYKFK3fdcolkm9EoUfTZQWf8KdNTUm31-FRwoNIyolmk7bAhfB2N64DRrgYlX7RSIvDqDJvWpru4y82q41_vFCJE3MYfeAaj8duRdYx-BBDtYGLBJJAqe9ayJ5gIrYf-OyM2PGzvVRV2hr41vFNVYfaP3tY-4eJ93Cksz1M4bLBaUL5nGtYPrZvKDolxbHOD1H9ygDWpNyZVQmVpudz0ArjNumLi7mn_HXrbfDGZw8768L8E3lp4GE9QbIZHDF77JjkbcO_BBJnqJMMBg9jD4ixHK4TlRslXFabSap_Fw

0 comments on commit 6117d96

Please sign in to comment.