반응형
build.gradle.kts
dependencies {
// ...
implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server")
implementation("io.jsonwebtoken:jjwt:0.9.1")
implementation("javax.xml.bind:jaxb-api:2.3.1")
}
UserToken
data class UserToken(
val id: String,
val name: String,
val expireAt: LocalDateTime,
)
IssueToken
class IssueToken {
data class Request(
val id: String,
val name: String,
)
data class Response(
val accessToken: String,
val tokenType: String,
val expireAt: LocalDateTime,
)
}
UserTokenService
@Service
class UserTokenService {
private val log = LoggerFactory.getLogger(this::class.java)
fun createToken(userToken: UserToken): String {
return Jwts.builder()
.setSubject("user")
.claim("id", userToken.id)
.claim("name", userToken.name)
.setExpiration(userToken.expireAt.toDate())
.signWith(SignatureAlgorithm.HS512, SIGNING_KEY.toByteArray())
.compact()
}
fun parseToken(token: String): UserToken? {
return try {
val claims = Jwts.parser()
.setSigningKey(SIGNING_KEY.toByteArray())
.parseClaimsJws(token)
.body
UserToken(
id = claims.get("id", String::class.java),
name = claims.get("name", String::class.java),
expireAt = claims.expiration.toLocalDateTime(),
)
} catch (e: Exception) {
log.error("Jwt Parsing Error", e)
null
}
}
private fun LocalDateTime.toDate(): Date {
return Date.from(this.atZone(ZoneId.systemDefault()).toInstant())
}
private fun Date.toLocalDateTime(): LocalDateTime {
return this.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
}
companion object {
private const val SIGNING_KEY = "test-signing-key"
}
}
UserTokenAuthenticationFilter
@Component
class UserTokenAuthenticationFilter(
private val userTokenService: UserTokenService,
) : OncePerRequestFilter() {
private val bearerTokenResolver: BearerTokenResolver = DefaultBearerTokenResolver()
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
val accessToken = bearerTokenResolver.resolve(request)
if (accessToken != null) {
val userToken = userTokenService.parseToken(accessToken)
val authentication = object : AbstractAuthenticationToken(listOf()) {
override fun getPrincipal() = userToken
override fun getCredentials() = accessToken
override fun isAuthenticated() = userToken != null
}
SecurityContextHolder.getContext().authentication = authentication
}
filterChain.doFilter(request, response)
}
}
SecurityConfig
@Configuration
@EnableWebSecurity(debug = true)
class SecurityConfig {
@Bean
fun securityFilterChain(
http: HttpSecurity,
userTokenAuthenticationFilter: UserTokenAuthenticationFilter,
): SecurityFilterChain {
return http
.authorizeHttpRequests { authorize ->
authorize.requestMatchers("/api/v*/auth/token").permitAll()
authorize.anyRequest().authenticated()
}
.addFilterAfter(userTokenAuthenticationFilter, LogoutFilter::class.java)
.csrf { it.disable() }
.build()
}
}
AuthController
@RestController
class AuthController(
private val userTokenService: UserTokenService,
) {
@PostMapping("/api/v1/auth/token")
fun issueToken(@RequestBody request: IssueToken.Request): IssueToken.Response {
val userToken = UserToken(request.id, request.name, LocalDateTime.now().plusMinutes(10))
val token = userTokenService.createToken(userToken)
return IssueToken.Response(
accessToken = token,
tokenType = "Bearer",
expireAt = userToken.expireAt,
)
}
@GetMapping("/api/v1/auth/user")
fun getUser(@AuthenticationPrincipal userToken: UserToken): UserToken {
return userToken
}
}
토큰 발급
% curl -X POST "http://localhost:8080/api/v1/auth/token" \
-d '{"id": "123", "name": "tyler"}' \
-H 'Content-Type: application/json'
{"accessToken":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwiaWQiOiIxMjMiLCJuYW1lIjoidHlsZXIiLCJleHAiOjE3MzM0MTU3NzN9.mxJEtzkCvslMJ2KyG5yfyfGPIS5aUTapuunHqvzmZ4-lu3vKAm5PjtYLzvxP3Yb3jbLadfNpc6Axa1Z0ff4ysg","tokenType":"Bearer","expireAt":"2024-12-06T01:22:53.506523"}
리소스 요청
% curl -X GET "http://localhost:8080/api/v1/auth/user" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwiaWQiOiIxMjMiLCJuYW1lIjoidHlsZXIiLCJleHAiOjE3MzM0MTU3NzN9.mxJEtzkCvslMJ2KyG5yfyfGPIS5aUTapuunHqvzmZ4-lu3vKAm5PjtYLzvxP3Yb3jbLadfNpc6Axa1Z0ff4ysg'
{"id":"123","name":"tyler","expireAt":"2024-12-06T01:22:53"}
반응형
'Development > Spring Security' 카테고리의 다른 글
[Spring Security] Test (0) | 2023.10.29 |
---|---|
[Spring Security] WebSocketSecurity (0) | 2023.10.29 |
[Spring Security] OAuth2 Authentication (0) | 2023.10.29 |
[Spring Security] Basic Authentication (0) | 2023.10.29 |
[Spring Security] Form Authentication (0) | 2023.10.29 |