From ae91a48c29306f01ffd3d3690615ef556975cf70 Mon Sep 17 00:00:00 2001 From: zionhann Date: Wed, 23 Oct 2024 15:31:26 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=8B=9C=20=EC=95=A1=EC=84=B8=EC=8A=A4=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EA=B3=BC=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/chillin/auth/AuthService.kt | 20 ++++++++++++--- .../kotlin/com/chillin/auth/JwtProvider.kt | 25 +++++++++++++++---- .../com/chillin/auth/TokenExpiration.kt | 6 +++++ .../chillin/auth/response/TokenResponse.kt | 4 +-- 4 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/com/chillin/auth/TokenExpiration.kt diff --git a/src/main/kotlin/com/chillin/auth/AuthService.kt b/src/main/kotlin/com/chillin/auth/AuthService.kt index 1bd959f..8b4c35b 100644 --- a/src/main/kotlin/com/chillin/auth/AuthService.kt +++ b/src/main/kotlin/com/chillin/auth/AuthService.kt @@ -1,14 +1,28 @@ package com.chillin.auth import com.chillin.auth.response.TokenResponse +import com.chillin.redis.RedisKeyFactory +import org.slf4j.LoggerFactory +import org.springframework.data.redis.core.StringRedisTemplate import org.springframework.stereotype.Service @Service class AuthService( - private val jwtProvider: JwtProvider + private val jwtProvider: JwtProvider, + private val redisTemplate: StringRedisTemplate ) { fun issueToken(accountId: String): TokenResponse { - val token = jwtProvider.issueToken(accountId) - return TokenResponse(token, jwtProvider.expirationSeconds) + logger.info("Issuing token...") + val tokens = jwtProvider.issueToken(accountId) + + logger.info("Saving refresh token to redis...") + val tokenKeyName = RedisKeyFactory.create(accountId, "refresh-token") + redisTemplate.opsForValue().set(tokenKeyName, tokens.refreshToken) + + return tokens + } + + companion object { + private val logger = LoggerFactory.getLogger(AuthService::class.java) } } diff --git a/src/main/kotlin/com/chillin/auth/JwtProvider.kt b/src/main/kotlin/com/chillin/auth/JwtProvider.kt index c7dcffc..abad526 100644 --- a/src/main/kotlin/com/chillin/auth/JwtProvider.kt +++ b/src/main/kotlin/com/chillin/auth/JwtProvider.kt @@ -1,30 +1,45 @@ package com.chillin.auth +import com.chillin.auth.response.TokenResponse import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.Keys import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.NestedConfigurationProperty import java.util.* @ConfigurationProperties(prefix = "custom.jwt") class JwtProvider( private val secretKey: String, private val issuer: String, - val expirationSeconds: Long + + @NestedConfigurationProperty + private val expiration: TokenExpiration ) { - fun issueToken(accountId: String): String { + fun issueToken(accountId: String): TokenResponse { val iat = Date() - val exp = Date(iat.toInstant().plusSeconds(expirationSeconds).toEpochMilli()) val sig = Keys.hmacShaKeyFor(secretKey.toByteArray()) - return Jwts.builder() + val accessToken = Jwts.builder() + .claims() + .subject(accountId) + .issuer(issuer) + .issuedAt(iat) + .expiration(Date(iat.toInstant().plusSeconds(expiration.accessToken).toEpochMilli())) + .and() + .signWith(sig, Jwts.SIG.HS256) + .compact() + + val refreshToken = Jwts.builder() .claims() .subject(accountId) .issuer(issuer) .issuedAt(iat) - .expiration(exp) + .expiration(Date(iat.toInstant().plusSeconds(expiration.refreshToken).toEpochMilli())) .and() .signWith(sig, Jwts.SIG.HS256) .compact() + + return TokenResponse(accessToken, refreshToken) } } \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/auth/TokenExpiration.kt b/src/main/kotlin/com/chillin/auth/TokenExpiration.kt new file mode 100644 index 0000000..52aac23 --- /dev/null +++ b/src/main/kotlin/com/chillin/auth/TokenExpiration.kt @@ -0,0 +1,6 @@ +package com.chillin.auth + +class TokenExpiration( + val accessToken: Long, + val refreshToken: Long +) diff --git a/src/main/kotlin/com/chillin/auth/response/TokenResponse.kt b/src/main/kotlin/com/chillin/auth/response/TokenResponse.kt index 1ffa81e..e829733 100644 --- a/src/main/kotlin/com/chillin/auth/response/TokenResponse.kt +++ b/src/main/kotlin/com/chillin/auth/response/TokenResponse.kt @@ -1,7 +1,7 @@ package com.chillin.auth.response data class TokenResponse( - val token: String, - val expiresIn: Long, + val accessToken: String, + val refreshToken: String, val grantType: String = "Bearer" ) From a7cf46b3058262b929b5203dc06fbd6cdc0f44dc Mon Sep 17 00:00:00 2001 From: zionhann Date: Wed, 23 Oct 2024 17:02:01 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=ED=86=A0=ED=81=B0=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EB=B0=8F=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/chillin/auth/AuthController.kt | 13 ++++++++++ .../kotlin/com/chillin/auth/AuthService.kt | 26 ++++++++++++++++++- .../kotlin/com/chillin/auth/JwtProvider.kt | 20 ++++++++++++-- .../chillin/exception/ExceptionController.kt | 18 +++++++++++++ .../chillin/exception/ExceptionResponse.kt | 7 +++++ 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/chillin/exception/ExceptionController.kt create mode 100644 src/main/kotlin/com/chillin/exception/ExceptionResponse.kt diff --git a/src/main/kotlin/com/chillin/auth/AuthController.kt b/src/main/kotlin/com/chillin/auth/AuthController.kt index 01e8683..f306c4e 100644 --- a/src/main/kotlin/com/chillin/auth/AuthController.kt +++ b/src/main/kotlin/com/chillin/auth/AuthController.kt @@ -4,8 +4,12 @@ import com.chillin.auth.appleid.AppleIdService import com.chillin.auth.request.SignInWithAppleRequest import com.chillin.auth.response.TokenResponse import com.chillin.member.MemberService +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -22,4 +26,13 @@ class AuthController( memberService.register(accountId, refreshToken) return authService.issueToken(accountId) } + + @PostMapping("/oauth2/refresh") + fun refreshToken(@RequestHeader(HttpHeaders.AUTHORIZATION) bearerToken: String): ResponseEntity { + val token = bearerToken.substringAfter("Bearer").trim() + val newToken = authService.reissueToken(token) + + return if (newToken != null) ResponseEntity.ok(newToken) + else ResponseEntity.status(HttpStatus.UNAUTHORIZED).build() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/auth/AuthService.kt b/src/main/kotlin/com/chillin/auth/AuthService.kt index 8b4c35b..a33dfe4 100644 --- a/src/main/kotlin/com/chillin/auth/AuthService.kt +++ b/src/main/kotlin/com/chillin/auth/AuthService.kt @@ -22,7 +22,31 @@ class AuthService( return tokens } + fun reissueToken(token: String): TokenResponse? { + logger.info("Validating token...") + val payload = jwtProvider.validate(token) + val accountId = payload.subject + + val tokenKeyName = RedisKeyFactory.create(accountId, "refresh-token") + val storedToken = + redisTemplate.opsForValue().get(tokenKeyName) ?: return null // if token is already invalidated + + return if (isReuseDetected(token, storedToken)) invalidateToken(tokenKeyName) + else issueToken(accountId) + } + + private fun isReuseDetected(token: String, storedToken: String): Boolean { + logger.info("Checking if token is reused...") + return token != storedToken + } + + private fun invalidateToken(tokenKeyName: String): TokenResponse? { + logger.info("Invalidating token...") + redisTemplate.delete(tokenKeyName) + return null + } + companion object { private val logger = LoggerFactory.getLogger(AuthService::class.java) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/auth/JwtProvider.kt b/src/main/kotlin/com/chillin/auth/JwtProvider.kt index abad526..e5fa18b 100644 --- a/src/main/kotlin/com/chillin/auth/JwtProvider.kt +++ b/src/main/kotlin/com/chillin/auth/JwtProvider.kt @@ -1,6 +1,8 @@ package com.chillin.auth import com.chillin.auth.response.TokenResponse +import io.jsonwebtoken.Claims +import io.jsonwebtoken.JwtException import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.Keys import org.springframework.boot.context.properties.ConfigurationProperties @@ -9,16 +11,17 @@ import java.util.* @ConfigurationProperties(prefix = "custom.jwt") class JwtProvider( - private val secretKey: String, + secretKey: String, private val issuer: String, @NestedConfigurationProperty private val expiration: TokenExpiration ) { + private val sig = Keys.hmacShaKeyFor(secretKey.toByteArray()) + fun issueToken(accountId: String): TokenResponse { val iat = Date() - val sig = Keys.hmacShaKeyFor(secretKey.toByteArray()) val accessToken = Jwts.builder() .claims() @@ -42,4 +45,17 @@ class JwtProvider( return TokenResponse(accessToken, refreshToken) } + + fun validate(token: String): Claims { + try { + return Jwts.parser() + .verifyWith(sig) + .requireIssuer(issuer) + .build() + .parseSignedClaims(token) + .payload + } catch (e: JwtException) { + throw JwtException("Failed to verify token", e) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/exception/ExceptionController.kt b/src/main/kotlin/com/chillin/exception/ExceptionController.kt new file mode 100644 index 0000000..286c168 --- /dev/null +++ b/src/main/kotlin/com/chillin/exception/ExceptionController.kt @@ -0,0 +1,18 @@ +package com.chillin.exception + +import io.jsonwebtoken.JwtException +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice + +@RestControllerAdvice +class ExceptionController { + + @ExceptionHandler(JwtException::class) + fun handleJwtException(e: JwtException): ResponseEntity { + val response = + ExceptionResponse(401, HttpStatus.UNAUTHORIZED.reasonPhrase, e.message ?: "Failed to verify token") + return ResponseEntity.status(401).body(response) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/exception/ExceptionResponse.kt b/src/main/kotlin/com/chillin/exception/ExceptionResponse.kt new file mode 100644 index 0000000..e3d479b --- /dev/null +++ b/src/main/kotlin/com/chillin/exception/ExceptionResponse.kt @@ -0,0 +1,7 @@ +package com.chillin.exception + +class ExceptionResponse( + val code: Int, + val description: String, + val message: String, +) \ No newline at end of file From 80116f6b6c16759f29a81cb43d1eb35801024713 Mon Sep 17 00:00:00 2001 From: zionhann Date: Thu, 24 Oct 2024 15:14:50 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 +- .../security/JwtAuthenticationFilter.kt | 37 +++++++++++++++++++ .../com/chillin/security/SecurityConfig.kt | 35 ++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/chillin/security/JwtAuthenticationFilter.kt create mode 100644 src/main/kotlin/com/chillin/security/SecurityConfig.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8e1c0f8..a521e99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-web") -// implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-security") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.springframework.ai:spring-ai-openai-spring-boot-starter") diff --git a/src/main/kotlin/com/chillin/security/JwtAuthenticationFilter.kt b/src/main/kotlin/com/chillin/security/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..77dce28 --- /dev/null +++ b/src/main/kotlin/com/chillin/security/JwtAuthenticationFilter.kt @@ -0,0 +1,37 @@ +package com.chillin.security + +import com.chillin.auth.JwtProvider +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.HttpHeaders +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.filter.OncePerRequestFilter + +class JwtAuthenticationFilter( + private val jwtProvider: JwtProvider +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val token = request.getHeader(HttpHeaders.AUTHORIZATION) + ?.substringAfter(BEARER) + ?.trim() + + if (token != null) { + val accountId = jwtProvider.validate(token).subject + val authentication = UsernamePasswordAuthenticationToken(accountId, null, emptyList()) + + SecurityContextHolder.getContext().authentication = authentication + } + filterChain.doFilter(request, response) + } + + companion object { + private const val BEARER = "Bearer" + } +} diff --git a/src/main/kotlin/com/chillin/security/SecurityConfig.kt b/src/main/kotlin/com/chillin/security/SecurityConfig.kt new file mode 100644 index 0000000..8e43e9d --- /dev/null +++ b/src/main/kotlin/com/chillin/security/SecurityConfig.kt @@ -0,0 +1,35 @@ +package com.chillin.security + +import com.chillin.auth.JwtProvider +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +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.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val jwtProvider: JwtProvider +) { + + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + return http + .authorizeHttpRequests { auth -> + auth + .requestMatchers("/auth/oauth2/**", "/scan").permitAll() + .anyRequest().authenticated() + } + .addFilterBefore(JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter::class.java) + .sessionManagement { session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .csrf { it.disable() } + .httpBasic { it.disable() } + .formLogin { it.disable() } + .build() + } +} \ No newline at end of file From 6ad977b9bef1ff46ee54acaaafb09016271e4a60 Mon Sep 17 00:00:00 2001 From: zionhann Date: Thu, 24 Oct 2024 18:27:21 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EA=B3=84=EC=A0=95=EB=B3=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/chillin/drawing/DrawingController.kt | 26 ++++++++++++++----- .../com/chillin/drawing/DrawingRepository.kt | 5 +++- .../com/chillin/drawing/DrawingService.kt | 7 +++-- .../com/chillin/drawing/domain/Drawing.kt | 8 ++++++ .../chillin/epson/EpsonConnectController.kt | 12 ++++++--- .../com/chillin/member/MemberRepository.kt | 1 + .../com/chillin/member/MemberService.kt | 4 +++ .../com/chillin/security/SecurityConfig.kt | 2 +- 8 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/chillin/drawing/DrawingController.kt b/src/main/kotlin/com/chillin/drawing/DrawingController.kt index 294120c..82fd829 100644 --- a/src/main/kotlin/com/chillin/drawing/DrawingController.kt +++ b/src/main/kotlin/com/chillin/drawing/DrawingController.kt @@ -1,17 +1,19 @@ package com.chillin.drawing -import com.chillin.epson.EpsonConnectService -import com.chillin.epson.request.PrintSettingsRequest import com.chillin.drawing.request.ImageGenerationRequest import com.chillin.drawing.request.ImagePrintRequest import com.chillin.drawing.response.DrawingResponse import com.chillin.drawing.response.DrawingResponseWrapper +import com.chillin.epson.EpsonConnectService +import com.chillin.epson.request.PrintSettingsRequest +import com.chillin.member.MemberService import com.chillin.openai.DallEService import com.chillin.s3.S3Service import com.chillin.type.DrawingType import com.chillin.type.MediaSubtype import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -26,16 +28,22 @@ class DrawingController( private val dallEService: DallEService, private val s3Service: S3Service, private val drawingService: DrawingService, - private val epsonConnectService: EpsonConnectService + private val epsonConnectService: EpsonConnectService, + private val memberService: MemberService ) { @PostMapping("/gen") - fun generateDrawing(@RequestBody imageGenerationRequest: ImageGenerationRequest): ResponseEntity { + fun generateDrawing( + @RequestBody imageGenerationRequest: ImageGenerationRequest, + @AuthenticationPrincipal accountId: String + ): ResponseEntity { val rawPrompt = imageGenerationRequest.prompt val pathname = "generated/${UUID.randomUUID()}.${MediaSubtype.JPEG.value}" val (url, revisedPrompt) = dallEService.generateImage(rawPrompt) val presignedUrl = s3Service.uploadImage(pathname, url, revisedPrompt) - val savedImage = drawingService.save(pathname, DrawingType.GENERATED, rawPrompt, revisedPrompt) + + val member = memberService.findMemberByAccountId(accountId) + val savedImage = drawingService.save(member, pathname, DrawingType.GENERATED, rawPrompt, revisedPrompt) val responseBody = DrawingResponse(savedImage.drawingId, presignedUrl, rawPrompt) return ResponseEntity.status(HttpStatus.CREATED).body(responseBody) @@ -53,8 +61,12 @@ class DrawingController( } @GetMapping - fun getDrawings(@RequestParam type: DrawingType): DrawingResponseWrapper { - val data = drawingService.getAllByType(type).map { drawing -> + fun getDrawings( + @RequestParam type: DrawingType, + @AuthenticationPrincipal accountId: String + ): DrawingResponseWrapper { + val member = memberService.findMemberByAccountId(accountId) + val data = drawingService.getMyDrawingsByType(type, member).map { drawing -> val url = s3Service.getImageUrl(drawing.pathname) DrawingResponse(drawing.drawingId, url, drawing.rawPrompt) } diff --git a/src/main/kotlin/com/chillin/drawing/DrawingRepository.kt b/src/main/kotlin/com/chillin/drawing/DrawingRepository.kt index 6e366f0..4447bef 100644 --- a/src/main/kotlin/com/chillin/drawing/DrawingRepository.kt +++ b/src/main/kotlin/com/chillin/drawing/DrawingRepository.kt @@ -1,9 +1,12 @@ package com.chillin.drawing; import com.chillin.drawing.domain.Drawing +import com.chillin.member.Member import com.chillin.type.DrawingType import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query interface DrawingRepository : JpaRepository { - fun findAllByTypeOrderByCreatedAtDesc(type: DrawingType): List + @Query("SELECT d FROM Drawing d WHERE d.type = :type AND d.member = :member ORDER BY d.createdAt DESC") + fun findAllByType(type: DrawingType, member: Member): List } \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/drawing/DrawingService.kt b/src/main/kotlin/com/chillin/drawing/DrawingService.kt index 1531f0d..4da8dd7 100644 --- a/src/main/kotlin/com/chillin/drawing/DrawingService.kt +++ b/src/main/kotlin/com/chillin/drawing/DrawingService.kt @@ -1,6 +1,7 @@ package com.chillin.drawing import com.chillin.drawing.domain.Drawing +import com.chillin.member.Member import com.chillin.type.DrawingType import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -12,13 +13,14 @@ class DrawingService( private val logger = LoggerFactory.getLogger(DrawingService::class.java) fun save( + member: Member, pathname: String, drawingType: DrawingType, rawPrompt: String? = null, revisedPrompt: String? = null ): Drawing { logger.info("Saving drawing to db...") - val drawing = Drawing(pathname, drawingType, rawPrompt, revisedPrompt) + val drawing = Drawing(member, pathname, drawingType, rawPrompt, revisedPrompt) return drawingRepository.save(drawing).apply { logger.info("Saved drawing to db: drawingId=${drawingId}, pathname=$pathname, drawingType=$drawingType, rawPrompt=$rawPrompt, revisedPrompt=$revisedPrompt") @@ -36,5 +38,6 @@ class DrawingService( } } - fun getAllByType(type: DrawingType) = drawingRepository.findAllByTypeOrderByCreatedAtDesc(type) + fun getMyDrawingsByType(type: DrawingType, member: Member) = + drawingRepository.findAllByType(type, member) } \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/drawing/domain/Drawing.kt b/src/main/kotlin/com/chillin/drawing/domain/Drawing.kt index 4b226ff..840b8cc 100644 --- a/src/main/kotlin/com/chillin/drawing/domain/Drawing.kt +++ b/src/main/kotlin/com/chillin/drawing/domain/Drawing.kt @@ -1,14 +1,18 @@ package com.chillin.drawing.domain +import com.chillin.member.Member import com.chillin.type.DrawingType import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EntityListeners import jakarta.persistence.EnumType import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener @@ -19,6 +23,10 @@ import java.time.LocalDateTime @EntityListeners(AuditingEntityListener::class) class Drawing( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + val member: Member, + @Column val pathname: String, diff --git a/src/main/kotlin/com/chillin/epson/EpsonConnectController.kt b/src/main/kotlin/com/chillin/epson/EpsonConnectController.kt index 8230584..f4ef212 100644 --- a/src/main/kotlin/com/chillin/epson/EpsonConnectController.kt +++ b/src/main/kotlin/com/chillin/epson/EpsonConnectController.kt @@ -2,10 +2,12 @@ package com.chillin.epson import com.chillin.adobe.AdobeService import com.chillin.drawing.DrawingService +import com.chillin.member.MemberService import com.chillin.s3.S3Service import com.chillin.type.DrawingType import com.chillin.type.MediaSubtype import org.slf4j.LoggerFactory +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @@ -16,13 +18,15 @@ import java.util.* class EpsonConnectController( private val drawingService: DrawingService, private val s3Service: S3Service, - private val adobeService: AdobeService + private val adobeService: AdobeService, + private val memberService: MemberService ) { private val logger = LoggerFactory.getLogger(EpsonConnectController::class.java) - @PostMapping("/scan") - fun receiveScanData(@RequestParam files: Map) { + @PostMapping("/scan/{accountId}") + fun receiveScanData(@RequestParam files: Map, @PathVariable accountId: String) { logger.info("Received {} files={}", files.values.size, files.values.map(MultipartFile::getOriginalFilename)) + val member = memberService.findMemberByAccountId(accountId) files.values.forEach { file -> val mediaSubtype = MediaSubtype.parse(file.contentType) @@ -32,7 +36,7 @@ class EpsonConnectController( val uploadUrl = s3Service.getImageUrlForPOST(pathname) adobeService.cutout(downloadUrl, uploadUrl) - drawingService.save(pathname, DrawingType.SCANNED) + drawingService.save(member, pathname, DrawingType.SCANNED) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/chillin/member/MemberRepository.kt b/src/main/kotlin/com/chillin/member/MemberRepository.kt index 86ddc08..dd46d75 100644 --- a/src/main/kotlin/com/chillin/member/MemberRepository.kt +++ b/src/main/kotlin/com/chillin/member/MemberRepository.kt @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository @Repository interface MemberRepository : JpaRepository { + fun findByAccountId(accountId: String): Member? } diff --git a/src/main/kotlin/com/chillin/member/MemberService.kt b/src/main/kotlin/com/chillin/member/MemberService.kt index 8e36269..d8c154d 100644 --- a/src/main/kotlin/com/chillin/member/MemberService.kt +++ b/src/main/kotlin/com/chillin/member/MemberService.kt @@ -14,6 +14,10 @@ class MemberService( memberRepository.save(member) } + fun findMemberByAccountId(accountId: String): Member { + return memberRepository.findByAccountId(accountId) ?: throw RuntimeException("Member not found") + } + companion object { private val logger = LoggerFactory.getLogger(MemberService::class.java) } diff --git a/src/main/kotlin/com/chillin/security/SecurityConfig.kt b/src/main/kotlin/com/chillin/security/SecurityConfig.kt index 8e43e9d..0ba2aaf 100644 --- a/src/main/kotlin/com/chillin/security/SecurityConfig.kt +++ b/src/main/kotlin/com/chillin/security/SecurityConfig.kt @@ -20,7 +20,7 @@ class SecurityConfig( return http .authorizeHttpRequests { auth -> auth - .requestMatchers("/auth/oauth2/**", "/scan").permitAll() + .requestMatchers("/auth/oauth2/**", "/scan/**").permitAll() .anyRequest().authenticated() } .addFilterBefore(JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter::class.java) From 9fbefb5e6c434dd4307cfc753c7aa0a692b53690 Mon Sep 17 00:00:00 2001 From: zionhann Date: Thu, 24 Oct 2024 18:43:41 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=EB=AA=A8=EC=85=98=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B3=84=EC=A0=95?= =?UTF-8?q?=EB=B3=84=20=EA=B4=80=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/chillin/motion/MotionController.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/chillin/motion/MotionController.kt b/src/main/kotlin/com/chillin/motion/MotionController.kt index d75e784..7d4e7d1 100644 --- a/src/main/kotlin/com/chillin/motion/MotionController.kt +++ b/src/main/kotlin/com/chillin/motion/MotionController.kt @@ -2,12 +2,14 @@ package com.chillin.motion import com.chillin.drawing.DrawingService import com.chillin.drawing.response.DrawingResponse +import com.chillin.member.MemberService import com.chillin.motion.request.MotionRequest import com.chillin.s3.S3Service import com.chillin.type.DrawingType import com.chillin.type.MediaSubtype import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -20,17 +22,23 @@ import java.util.* class MotionController( private val drawingService: DrawingService, private val s3Service: S3Service, - private val motionService: MotionService + private val motionService: MotionService, + private val memberService: MemberService ) { @PostMapping("/{drawingId}") - fun addMotion(@PathVariable drawingId: Long, @RequestBody request: MotionRequest): ResponseEntity { + fun addMotion( + @PathVariable drawingId: Long, + @RequestBody request: MotionRequest, + @AuthenticationPrincipal accountId: String + ): ResponseEntity { val srcPathname = drawingService.getNameById(drawingId) val (jpegBytes, _) = s3Service.getImageData(srcPathname) val gifBytes = motionService.addMotion(srcPathname, jpegBytes, request.motionType) val targetPathname = "animated/${UUID.randomUUID()}.${MediaSubtype.GIF.value}" val (uploadedFilePathname, url) = s3Service.uploadImage(targetPathname, gifBytes) - val savedImage = drawingService.save(uploadedFilePathname, DrawingType.ANIMATED) + val member = memberService.findMemberByAccountId(accountId) + val savedImage = drawingService.save(member, uploadedFilePathname, DrawingType.ANIMATED) val response = DrawingResponse(savedImage.drawingId, url) return ResponseEntity.status(HttpStatus.CREATED).body(response)